From 83e61655854d6e7e189841ef121a993d9a01c855 Mon Sep 17 00:00:00 2001 From: Bota Date: Sun, 9 Aug 2026 00:05:48 +0200 Subject: [PATCH] diy: add support for Waveshare Touch LCD 3.5 ESP32-S3 board with a 3.5inch 480x320 capacitive touch display (ST7796/FT6336), OV5640 camera and AXP2101 pmu. The pmu, the touchscreen and the camera sccb share the same i2c pins, so the pmu attaches to the bus owned by the touchscreen once it exists, and the touchscreen is now stopped before esp_camera_init() rather than only being restarted after it - otherwise the sccb cannot claim the pins. The 3.5B variant uses a different (QSPI) display controller and is not supported. --- ...g_display_waveshares3_touch_lcd35.defaults | 85 ++++++ diy/README.md | 18 ++ diy/img/ws-touch-lcd35.jpg | Bin 0 -> 26011 bytes diy/waveshare/README.md | 13 + gitlab/diy_fw.yml | 3 + main/Kconfig.projbuild | 54 +++- main/camera.c | 11 +- main/display.c | 4 +- main/display_hw.c | 5 +- main/input/touchscreen.inc | 29 ++ main/power.c | 3 + main/power/wslcdtouch35.inc | 286 ++++++++++++++++++ main/process/dashboard.c | 14 +- main/process/ota_defines.h | 2 + main/ui/dashboard.c | 3 +- 15 files changed, 507 insertions(+), 23 deletions(-) create mode 100644 configs/sdkconfig_display_waveshares3_touch_lcd35.defaults create mode 100644 diy/img/ws-touch-lcd35.jpg create mode 100644 main/power/wslcdtouch35.inc diff --git a/configs/sdkconfig_display_waveshares3_touch_lcd35.defaults b/configs/sdkconfig_display_waveshares3_touch_lcd35.defaults new file mode 100644 index 000000000..cae3d167d --- /dev/null +++ b/configs/sdkconfig_display_waveshares3_touch_lcd35.defaults @@ -0,0 +1,85 @@ +# display waveshares3 touch lcd35 +CONFIG_APP_REPRODUCIBLE_BUILD=y +CONFIG_BF20A6_SUPPORT=n +CONFIG_BF3005_SUPPORT=n +CONFIG_BOARD_TYPE_WS_TOUCH_LCD35=y +CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y +CONFIG_BOOTLOADER_WDT_ENABLE=n +CONFIG_BT_ENABLED=y +CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=517 +CONFIG_BT_NIMBLE_ENABLED=y +CONFIG_BT_NIMBLE_GAP_DEVICE_NAME_MAX_LEN=11 +CONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=5632 +CONFIG_BT_NIMBLE_LOG_LEVEL_NONE=y +CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1 +CONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=y +CONFIG_BT_NIMBLE_NVS_PERSIST=y +CONFIG_BT_NIMBLE_ROLE_BROADCASTER=n +CONFIG_BT_NIMBLE_ROLE_CENTRAL=n +CONFIG_BT_NIMBLE_ROLE_OBSERVER=n +CONFIG_BT_NIMBLE_SM_LEGACY=n +CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME="j" +CONFIG_BUTTON_LONG_PRESS_HOLD_SERIAL_TIME_MS=100 +CONFIG_BUTTON_LONG_PRESS_TIME_MS=500 +CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y +CONFIG_COMPILER_WARN_WRITE_STRINGS=y +CONFIG_DEBUG_MODE=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESP_CONSOLE_NONE=y +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESP_ERR_TO_NAME_LOOKUP=n +CONFIG_ESP_MAIN_TASK_STACK_SIZE=12288 +CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=n +CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND=n +CONFIG_ESP_SYSTEM_BBPLL_RECALIB=n +CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT=y +CONFIG_ESP_TASK_WDT_EN=n +CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=n +CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=n +CONFIG_ESP_WIFI_IRAM_OPT=n +CONFIG_ESP_WIFI_MBEDTLS_TLS_CLIENT=n +CONFIG_ESP_WIFI_NVS_ENABLED=n +CONFIG_ESP_WIFI_RX_IRAM_OPT=n +CONFIG_ESP_WIFI_SOFTAP_SUPPORT=n +CONFIG_ETH_USE_SPI_ETHERNET=n +CONFIG_FATFS_LFN_HEAP=y +CONFIG_FATFS_VFS_FSTAT_BLKSIZE=4096 +CONFIG_FATFS_VOLUME_COUNT=1 +CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=3 +CONFIG_GC032A_SUPPORT=n +CONFIG_GC2145_SUPPORT=n +CONFIG_IDF_TARGET="esp32s3" +CONFIG_LIBC_NEWLIB_NANO_FORMAT=y +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n +CONFIG_MBEDTLS_ECP_RESTARTABLE=y +CONFIG_LIBC_STDIN_LINE_ENDING_LF=y +CONFIG_LIBC_STDOUT_LINE_ENDING_LF=y +CONFIG_NT99141_SUPPORT=n +CONFIG_OV3660_SUPPORT=n +CONFIG_OV7670_SUPPORT=n +CONFIG_OV7725_SUPPORT=n +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitionss3.csv" +CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP=n +CONFIG_SC030IOT_SUPPORT=n +CONFIG_SPIRAM=y +CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=256 +CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=65536 +CONFIG_SPIRAM_MEMTEST=n +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +CONFIG_SPI_FLASH_VERIFY_WRITE=y +CONFIG_SPI_MASTER_ISR_IN_IRAM=n +CONFIG_SPI_SLAVE_ISR_IN_IRAM=n +CONFIG_TINYUSB_CDC_ENABLED=y +CONFIG_TINYUSB_CDC_RX_BUFSIZE=64 +CONFIG_TINYUSB_DESC_MANUFACTURER_STRING="Waveshare" +CONFIG_TINYUSB_DESC_PRODUCT_STRING="S3 Touch LCD 3.5" diff --git a/diy/README.md b/diy/README.md index e551577ab..7bf79aee2 100644 --- a/diy/README.md +++ b/diy/README.md @@ -147,6 +147,24 @@ There are a number of hardware devices that can run Jade firmware with minimal c (BT enabled, see next section for disabling) * [Assembly Guide & Hardware Notes](./waveshare/) +### Waveshare S3 Touch LCD 3.5 +![](img/ws-touch-lcd35.jpg) +* Costs ~$26 USD from manufacturer ($33 USD for the "-C" version with case and OV5640 camera) +* 3.5 inch 320x480 IPS display (ST7796), touch screen interface (virtual buttons) +* Wifi/Bluetooth +* AXP2101 power management with battery charging and level indicator +* Easy USB debugging and flashing +* Typically supports Secure Boot V2 +* Hardware Required (No Soldering Required) + * [Waveshare S3 Touch LCD 3.5](https://www.waveshare.com/esp32-s3-touch-lcd-3.5.htm) (SKU 30733, or SKU 30934 for the -C version) + * OV5640/OV2640 Camera module (included in the -C version) + * 3.7V Lithium battery with MX1.25 connector (optional) +* NOTE: only the plain `3.5` model is supported - the `3.5B` variant uses a + different (QSPI) display controller and does NOT work with this firmware +* Build [config](../configs/sdkconfig_display_waveshares3_touch_lcd35.defaults) + (BT enabled, see next section for disabling) +* [Assembly Guide & Hardware Notes](./waveshare/) + # Modifying Configuration Files for Use Once you are familiar with the process of flashing the firmware using the sdkconfig templates that are included in the /config folder of this repository, there are some additional changes that you should make to these files before using the device with actual funds. diff --git a/diy/img/ws-touch-lcd35.jpg b/diy/img/ws-touch-lcd35.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fbd8a59a6581e8fa96bbc0eb78a893aa8601aea9 GIT binary patch literal 26011 zcmbTdbyV9;@HZOV3KSY#TPP4H9$W&YxI4uu6fG{r1Eok)+^x84p|}(%5-3*Op-6EG z!66WC`uyJezUSP(?!DoVlVoRi=QA_k-PxJhhxv!!fX6Bd$_fAs3=DuJ`Va811ds#Z zV&mZ8VB@0S@NjYQ2uKMD@bL+r5R(v*Qa+)gqI^O@K~2ZPNKN~cmV$y2#PpQ)IXeeC zH3Jt97aI=?8#~*-K`?Of@Cfh;$O#F_*=Q(e*#1Ai9=ZW!_!v%@Mpzin0GMPLSY#Lv zeE=o^00Rfj?LUV9*9!v^3mXR)51)XL2u;xV7=Vd^g@uWYg@%BQrVc{41F*?($e;4d z;y%%~!h7Zp6bMbo$7gw2*F&K*al$J2#^VD4AteI$_+t)t;9~}BQIW;{qJ2$_uxW2Ku^=Er$cW)nYdUk$sd3Akr`wtfe z0PBBX{cmLd4_stuT$tF{SlD>~aA9D+NB?7yVdFgI$0e85#?7R+Cu?lVz_oj&s-1-*MFGW18c;uDZ)C7&wi7h64d{ zAk_W?U=6d7JUfR9bxrZ2*j+#V?O+}rxYTVOdYqu+D&fz5f69q#Dy5=GuY-XQvH}oqf3ur4GJ+Th|8Fjp)wA`s^ zIrVnSJJJ-*JAm%pW^=(ml$C=>CDGrR+2YhaaJG1I)}z>$2vm1TR-%S*Ez==Pz1M|b z2{U0bJzVvJsapsAeUEO&un&4reWed+F8;J%C(1EHXs5D6PoZniieD4Z+@RUgw~3zK zjik=A@ocBW#-WFL=?xxzbwkUj;*>O(ro5@C=RA){xF}VO=m8K2YoE%qK>pSz()vko zpqc34k5j?w1*c)8}r>HV**oFe@{MdDrSIbD@+$HOCC zkZ6Nav3ZBq-!%noLae>*F24+R{a0=D{FsHxbW;B`wiFgpd8`p_vy}-*m$>D~i>3OP z*7p;#anL}*Bzr8ZJvD+bL^Rq0LG?^&Xv^y^qo#0C7mIZdQ^HwRRBw3zxaqm{W#6G& z@yDZm04z>sbj5Tz#s$O>)}R%jx_?m*a+q#aa{zoaL{ zk0>+%MfBO3fMktpngw)ULf?$GyIeGjc-+x?VM$ZP0HcVPw$Wd5eW8GhTWz(d+HCYQ zh<<^vZGbaa<(TfBq}l2gwvUe# zsKJY#I}~*MSfZnRWDycy~1DX4>r;zjdt zU2R>{^+p~59LyV)12^vyW{Z}KJj99welVrEmAbHQXC^Av#}BrYJhj=<6L``L1Q${~ zX_4B+u%t81DX8|4+K(oY{2}0O@al7J=SvLLBX$gcvJ;2!l@XVF46tT+jDcQkmK68^{ zQN5Pvu(0?aH8jpY5?xp}@9UKCt0wix@mO%a>mC^ZZSH2M7M)eL%;e*)`d>BA`dPX@ z9Xvqx$UG6Z3E>p;D8pdr7eOj&;%#>|)7u1}>j%VWsxxDi8^;Fx?Alb$1N$ybCb&05 zd%otbao~@@({F&^`&b~#a{G)q6-IXp1kL*>Rlgaxd|2}I}7NMo9L}0gVZaWrMwa2ON+>f6g-Um+}0M7CPnhe*849*zeNWdpWQzYZzuS+Q}CRI6ZbKUz}4GKT+L&`X| zUibg@8sn+ay#ETmF*0K;5`H&WX37=p{by&>FpR%BXb)Zlx%aPSA6O&cxS_m=yZ2<; zlvCr0Gm9#de3SatKyK9c=6Yuxyyp}Mzn?&XhomL0t*KSftJx5A;7hJ$o0JFsN5>#d z@Y&V(luc*ES#NLnq^sskvc8`*#C}HtY*KiUV*e-T!|r9yO*x<7YzG&L>i)y|uVRQv zD2jp(^~>M^(36)t@EaOVU&Rv_(2WI){wnlaAoy31k3Ik9>Tk4M3|tp64_He+IZ6o% zGVLjHQB0iE_ohgdBaXQG1OM>CSJcN8(%!T%h}2c=Y;L!Z4p~{^O*{YwVwLd(q|>Px z_Kad0&xiv35p<6Yh^1r>-&o%}f}|y{%hLcxWtUCtw^Lj3o-BLKEbeS2(fQgR1T8or za8jx!2=twFYNN4c45NS#t0SY)A^D07O7Di1$0o?5T@yj*WW^t5!NNWeN6TV|R3EQv zehEqOYGa2VnTzQQnb4W^{T)iry@drvn-1Pe+wh2BdaAS#N*F#_C;b#mix3{S(@9gr+6D01sh0ayV4L6>s_uaCjwHDs5U*K{9k?Bu6qW!tmP_RG*VQcn zjR^3M@)K4j=-_M$(e40GH2hCEFpjR=wfV+@m)6mt+Nqb|5cL-G2A>02ON?<9;10IC z45|JmJ4_3N!CuWf8mflZC&+2Dv&}RH^P*R0LjHisfz&kC|67c@-c6rnC;W2_p6X}Y zbt=K(jC9QJN7XztZ4_Zi)nF{@%ulhLiY%gKMF%p(LkF;AF+u@OA=XE8Vw<_t4k*gW z%>BL4_-Mt)lI{M4GEPq7qY)X7S5(bp-BALnO-V=2G>1l-$`EsnbCSFMGlP#X7`M8&Wj0fQ`gJdr24#t z;?Uh=aJO`(cUQN{QL5aEv+@-D>iT(=4|2MKH^C`<-MC3fjWl9Dx>LjhRV|1*}%a-6M{x_bL zrk=Y_L=J@xqx6+uYepjxwVw%wW?U4YOJyeg-`PA%=Xj?L$b}hM%rTu&W z*iR$nse$+*FCiWByos&IQCK!dq5VZcFEc+c3p?<`^9vzs`@FJ#fWfJ5OYv#aWR){b zUd1nLMm=9-$1mnMWTP~mscxH35w`S8s88It66quLObZok8D`DDFt>0>yRMc}3AVWo zx(yV>Y5I#8i`rVvlB49tX-{WDChC&+%E5;ugfIvQSSdsn6xtWQ`-4$l2GGTZ^GZCXr!fnrSBw!I78J(%_Vk+6-DA{xKRM+z;46rh zMfWKNd)7198E>dGk2osCt8$NM&4>eI{uJ>5Ah;7ZEIlFNO0#ti_{50XF2=t7RgkT~ z`H_8818+;Zp|F_UB^eMnzXPL23~;;&(A5x}jVG4efxV>0=$D{>k|_|}04Psdg8A== z^|NV+BQ#}Yh)8eGH?PAfSXBbI{__S#hBT;2FBMPA;u=+@1t2R-gLWv+3z+ z0WArQy`R>EJuOLxe*kS4TFHNy5W zEGI25ORzsqWZHq#!GJ^LFLe=I7(iz_<4C<24i3?>FM;OeKApcTw&ibcDReLI|CWMh{Wz#R<_YOK_{RK_Z8!Q!rkksy zFq4W|f zcOBWtzzQm+``+Ec09;|}OigiS7!FqFyN+Q0_9D?_nZ?Cm@C~)yjp1a1Vz%dOYf0y( z(V>w7wvBDJNh6pChiy@g&EQDGTzr}nf8X)#fG7|Uqn+q5`qy92jvCbvqN4!nt>%jc z$SX&`360YMlp8Y65Z)%}fT;ImK5ej!y@YmflodcaW#_hmTJt02<0JnpNEZn6hOaXH zjoaen^P|9e5!bH%rlkmvOHB;i9ic#fX0$j^%+T-(E3KMa;njzgl_+Y8oyC}8Q?(II z<4^6tgq>WP+M?Fx2E!y4Px@erKbKgPmB(gt{b|z96?P%fPEp^Ob|>z+N?=!y90Omi zl!NT6qX#l$DszBk0T`;52M-sun#}! z@$L#qd_8Js?OO54wdTdDDewn@R*m8PBpuB~EzL}WQ#B9Di3sV({nyeXQDGBDJ;-kZ z!gTF1z7Z3f!hL8X@BF-DD(qBFus*)ZA4)=6qTmSm3?-?$ds|93EX(ly8)opn%V$2+ zX6{x%5C~Zh^O1Z2IO(Lk21&qCxI1tsm)2U#iSGoajyd>^2=1n`66N1W#RXc)x3XKf zdxkkK+B&x+P1GokmiPGtd5?Xo;7-9nOB7(s&>^%mV7!!;EpG_2-h_~Ocjjy~B z-+F&hAPk#u^d45}0q|q% zQzJJ^pTMq~ce}*<#AU-8ijS8O02R!JLx3RQoW&!{oQ4BJ+vFEZjdF{)p+UK^@3xa6R-Dr((~0y zOahpR*rujUeAE{mgPg~17J?Y zVufL!`_B!{dkA)is#BSR&wFR5$J6NpiO04|MOYF{SRWMJmsU#~uSqwC{_;9699jh^ zcT_2Z?)Mg{ft&HN$FZ^$9l??-SxA%Kyl8Rf>3k|C)}v;O!oeH-Zpoe7X+sP8J6WOM zZIQC!dZu!GkBoNSq*(f*m=%A;o);`PeNxd?T}{}|ft*Me{_fPFBbHy!I(q!#WEAVI z83D_8eC7{aDf6uZTy==6&yA#%>Lt?o@8Xq97w^;GjgRGj!j6UGF@<|5R==5T`$FbV zc4(m@*?3;guK+TtOgLmr22$_o(5my|8ks8THFW)o=GUSbKXp+6QPpzdjH5;mXRNbhEot>K} zyK34mwpD*y@X>8$I3qqMYdow zrA7PC#qtl08`s#*WN?C{@GoaR+c&qxZfz9Q#Pv{RQs!23??k;|pXPoV^M$*>vf#Di z3W?^4U5!{d$E~N*iAC6XS2&FKFiU?5Tj8eIp3t<(tw)CbHn#C}CtApEPH6ax?2%=I z!BgwzW|>dqBos1?VO^d{=EJ1$%K1B9PC5+UC`P*l77ogZSMZPu5)A+V>tfvTq%!7g zn6)->q(6PLweoY4fz4}>8L$bdbF?W0irUOCHu`j=n%o|LR9bLxtQ$oN##dqmC>N%D zNEBZqgWB$%F7%LuJBX%Ef-?U23A|2zG7$n7iue9(`ax2xl=uD~f|N3ALC`l#E45FY zFJ(OY^vp0(WNd~SE3)4p%^2^G8IcMes-^A$uu%C$H%%{ySWTT~?0ny&X1Sy`oO0M~ z+NLf^u`UEmqQ7bQvl+l>bUP=s5hDh!ezV=rP-LWm#n%Igbesw66Y<&BtlwMo{x(FX zV9tSWS=|F4H9gu*SjbVZvmUY)V~d(fObUABo}T$6pVs}VBPPpynN53eyQ~Ejdam| z(Hss;;blchT@%p(K^?vq;s`a*1}!Xw-|66^5&Z5FTz1T28mvrQNint}Vy(Y4rG35b z<2y2x&zL$jufl<>A*0k5$Ala$)K^&B|FbJ$l$Yvup)k7!^_==;{?56Qs{OV z?6EQtUf0b=Jp-Gg?($J+9=y%DU00vYvGcB0y=`@}{)yrmZr6S_z7kpNF}wM=bh0^^ zZK%sQo@B6~j)hwOmhiZ!8XLXl)7uvp3FWh>^Y3jJ#{a_I`OxU+CFMe_!tP~nB2g%6 z^_OcUso$J=`h4TWH|M(7?IQt;8~*ruPwkf%*J{W)&2`tu!-b~rlDtwqm{t`e0^ZWT(!SXrKfYA zgT&5Etu+it_~|nB4nR|5r-9(fC{e9++z4~hFNcyptebRf7tO(u6dU342%b_PSp{>ex1Kp*r_o=%D-yk~tP=zg zD$O)1S7#mxbn#h*!B6j;#?MW@NFxV`{K|(e0~}mvbu?u>l<*eY6A(m7n&jmH_#^IJ zNte^_;&TcY8}jxloRtq)`o~z}+zS;xvM!S+IkheL-4y<;uAkE?WlqDosI{HDlktK+ z*;^E!H_>!Z_Excg2_hoavhjZE(QUgzvy5IK!@Y&n`qL$3ek-yxih?&`? zU8dikt+@PJ!nkg#A0GAhMdl&~O)R-y4xYJGxmG!&vXj&I%fFtsErF`NRxCQ)4V_2y zjrA)mW0DSjL4LJk-W*Rq{AF7HS;vFHwfZ$~vN2GZBH4KK=ib#g^qmq&V#m|w{S^Zj z$6WS>-SYaI{Fh{~dS$<0OhS%=gh}c=wCgO>*ALLy2;sosC@#*!?gut#VfqvTb*|8# z#p88g0Anq8WfcwpYl2Kc>@=XDxr4zzEr)foy5#d{gi}kR65`8GQ%neF4}+(f#fLlh znrm5}G=%^g5mriv>RAOHTnrsQ28U|w0Qs(-dpe8RJ9)ujo+M|6yVP5Mna2t}GJ}BZ zZ2A)^P#4J^X`a1Elc3`_b-&8By`+_1*Dc&;qo#{D&^V@@PCu7WgzA9!^X%DK|AIDE zt`Xyn=P9X*$dRh$56VMVMU%no*KpN|C8frh&<4)^0;7~wA(wok=+L{0)~!LUx?RIH zf)1&-zx{-qHT$?os%9;mVuce71XGr&9B#5=1texge(=Aw{tczR4sH0%pmEu4azhk= zpJM3ACkf3-=^NfWegcWmjIf!0)n+@+t-WdBE#mD}Q2=&f5BmHF*eR!a+bWe4L^ehbA&}Q zL=hr)J!=>ZPKP$#fhv8id)uTdy@TlM_>CU9sqz6_Pd^P3eDK@Mae`NWf9Bh?)+cxk zld{!35}W$zm!V&u9hk4wO-}+SRYlMZkRMDO9M`CLQ@`PZv(5Z?x}WdfV7IqeuD-g} zrri`-;n+qQx5*2;HIH$!cBZEdYH>E-9%JGGeetvGI@}@3_1rU%X1m7iecd(TdOlwT zj{56D{ADe1XsZubrSef9FVkP4R9@=dEZ++o zwOTsx|MES;$b!g)d9O$sInf^dxv2kY1*|>&%Rq1eG3Ncd z5Xq&}W=Fl^TSQaJ*ig_)Gx?DL6j?16^Bll;K~s}6P71VPN)9#c#WKlZN|)enh4717 z_{(^qsGZx2On(#Tp&Bn(nM0&gPG=*Vk;*b$ElRCBtKUn)zrSKHrwCUcwfcHVyj0Ec z+?OpS3u`tVX0(+VngkjnkerIu3`G8_%q>;|^aJ?numDr^mj^o3rvjZ7e3A4y_SBWp zcAZihj*^f0SL2)TO?5CmaK$HU@xrurq+%T=BDkaJ2`yiL^5nEeN7$ZGr$APLI#}Y? zsd)DZcj2$_{Jj<){X4Pga14$rzsyOYj|5CD!9MJLjR+AvrjR~J*~zQ1LR~s}_D$NJ zht3;Hjvp9#gC(6du+k7XgX0R?{o2$3y;W|gh((K(r!*(+d47>LoV$BUU3ALv<5NjJ zO&y_E=gi#3(Tch-*xJ3(c5zKg-REV3dl1N7L1H^S;+F=D8$3T&7OWj=0gTM^VBGRo0gzd+oWBixa^s`dW~Ic1r-7HaZF<69=m~de+spMlhqC6h4}L(ASy`Db9C~64d0x(UHOQrr;|f zE=!W^nP6qf?a){)Rt_@P72~_1U=SHC{QmmBe4=xnd#t=ACUMWDNYr#82#4gkrY#92H;>`*I7%7IKToYUpgJf#7iQj?>eR-Y|$jK0Xn@WDV9m*WVpXr?x@5qon-(@m@B!tlXa>XeUdXhjPh zN>ac${TtEII+hzrc+*-Ev4cgTaA21GqB=kXoIUw_JDL{Y3_PS2&cxhwmzQv+Q-8Xw zF&&YkcOtIi3^u|&Q`T{oo?fZWd_hGHb2BKO4=Q6A&>oc-cA2Jl_8Diu7UnZhj6XjL z5Xl6snlMgNSPIxvFLD5;nuI7ZI7*$ZWE9Q|MsUv*9G38ldd^F2p9(n07B4#RdBR?F zn_d==vhmM`W|MGyCBXv*aV$E>fDilF!TZ9wDyFnAurwTEeoeP)fC%ZmMk%p`P>2%7 zhO4p7i;_33=G%j6x=Gc-+2WI!vN{}l7iFPB;J~kVvms1H^yWW1Jz+?8W#0QQ_GYc8 zx{#~E+L(r(2f!1MVJXF>LR9zeEH(4?sx&&UURU{R5;yZnEU}mzX@~!mHFbH&vWL^N z9=Q*E{?qwU!@5f6rN9?0xakq!J+tkE$d<+W)f-|3)mXCaz!3`6$G>fh&q%8|if6cE zT4x&6*6=(dy;3{{u3{8v#dTMt5r2fJwDeh1C|or4vXFHD>hl{YTl!lpUFGyd&yyh> zow@Z9M(J1jdP+2RT3yFn+F!-&kyVkmgIxv@;k_1^5+F#m@sU0=O`GTLhibzT_b3Nq zP)Vi?|K1n(V-0In%;Qv{3rmP`S!KP=VU|?t_zz<`W0_IA2>awF>I=9K468p_(IU(PS-lgFNsAa&JYaqQjgIuArth5%_a$Y2tF%JMJ>Gj+zko~3GVHi@E?g0RK zd-W~*0q{`-EsnEo=yB8k5KBt?FN)mXxgu&}VN%HwL`Xx1|3fR-iyksg_yN!rg93M* zMEC}v0+&!Ik?T3dSO=NX!~A)R#g^k6ByVT-!iimE6ny@_)(P|x+amH4x+~izDzo`| zP8p`fajA6}eqR~;0LYz0E{w+ET)!7X5<%By(QIh{?jni1afe(Xx(3?RbV3#DBW-w( zOyA$3(pJab`SSK=+jgVt6aJC9p{EfX%gsgFuWgz!g4{J19vq!siZ z>0#Fin`IonuRV0+8PjOjK(eoDFZ3Kg?93HWz4*V#tRer%*ip7rch7>+iphJ0PO64N zW8V0#_1-eXMey=ed(B&B2s?Y@vAmEvXzBN~AUly{@+BBB=SPc_aI8@jDdzC&+@m9y z$9G>l;DsFZKOB@2$b-k#@3diHD#`W?rg>WCy`n`sR4d`9 zb2EDS7@l9A4*<#VcV7?mOX%J85~&)}Ji9y8Y2|BH;j}O8zwzm5hu}K#5zZkj;Zd$P z=7P7ep>m(bYm%#EFVawxF4(9(vgD#EVs>M9OG!So^~KZR2p}k`4cj3 zjHK!<5&6}y0A#3kCS!a6FxSQ&Ue9d9OD}4_20jm6?*u82aX&-r1jky}%K^*5ClSX; z|9CN9_MAc>o{FYwn6ZLb0Lvq@;QPx? z(_O41d(d5ah&|P(`YS0D;N_?^RE~cRe2VYh?0!{B?x8&35Hk3gAnATxUfm_{=f|@% zLJ&qoFDtYS5?>O}uOSfz?f$p2ZqeczSe<3_SBPgx`XtdK8fF$SUh^-0YiwRB9r3&q z$^tR^3EI?M2#xvn-*u6$oP98)m{9URGtG@FTFM(CK#t&W1S)yVpDtNkOfgw<_8T_P z|Aa2~Ynq1+J}t>~FL|PbzgX=6+IIADzjTO)ZvLp~bntyGIZozKJ|{wOcc9{UrnJ*H zoEZIQ0Wx??h`;zck#%6lvXC8%b)!9(aPPf=!FTqiwnpz3RS2(@=~nzayw)WJzPIR% z%3~EaNHTrL@;>?FrgY;ah3x*^3S zTrrs0IKY;?)V_WWV4luKdJQwMr5F>2s5_~1n%8##nyL@Y+nVbjrCWGv&bJ5V|8WO=S|vZO4u#e zk4)zd%3IqeT0<+hJwELg=EZJ$jGUIcgZ`B7wdF|pKNlr)2~DeyzWS+eYWnR^2^>8N z4YQL@KX}c^*V&w;@qNmb=i^*zGuIZC43iZgN2X`rsX~+7zKBz}m@FyeCGOTO30Ew_ zAU)A|?Lgyiy|OPQpP^%$GyU(ggBt_jTacdlOc#qIBHE$LHZ7x%uVf=fv6zi(Svn|T zGm~GlpL!$oQ9pI*n>1jbpfk@uN19xu5$$*@$z!q{>2Dt!7*~w>&*Vw6^!GBdbYYRp^fHvPb!yLc9(BPD}WiJ5%K9UW&I0V0uzB2Y>j((FUNfLITCby2Q62d6JQ7WUE zo$YNU4|vmz4Q)fm2JLErH@+-IfycvrK4vcweHLG*sQ%N-=g_lwjSE zqg-aKNXn}ld(nTV80bJ&zCTHLWJ)$5RztL5om;=eUbzF*H9w_qlDwUYTE1;9-zU<% z0Nf@*jWgRdvqj@x+Z%689`dA@jVHGFPFX?4ftS3|GW@6;avvN-5H7lfi1VOI$X z0+&r?brfVlo)o8V>?$Uvyq5P-&l1mt33Y0W9v-{&eHFv5O|HzQkCnNoQt_s6`)FBB zVV(1xt?8NRMbXyXN?&n7YE`$2TS00l03GCk*@TPg==@>^Ht0;Tt*god@o_lvbfz8? z8gt~yqQCY5wN+(478MtSkhdJeN*SNPtEZuZb8k&8`jg@wDRjn6LU*~1-ax{6L7BprSVs0nfln2QT!z&groeE^hb$IVnu zrFPu%N0&c;?s?In_J{DOf4=VWlfiFOyk}a--q`kU*(h&2z1c&uTqd!{<-AjoAii{Xx%ym4ptYjF^+y7jw6|f zNwOo&{-?mcS3fGoL@Q%Ct2SXpcprVoQ3?Tw2Y|Y|HAnS0ZL}}Cva|V^*y??4#{U?; z777^j`0q3!o{F{+9SLTRD10^(N8&(+5(7oQ;%ASqIp_8XC6U)d+9bp1*bxr%aUOe@D^<*7&C4Y`K}S$tRn1)azDVrv$UL<}}Yw{XAT9vdypqldnw~ zBJdZxvEM5+pEX*j*IO_^-xm3OLt4gJSzr0G+SN8Xa8?Mo2i16J_vlq?2tAw0P7ZcG z7ppMdEk60v|F^T^wF4QKydBT7G8-SkX3qV7N$dK(syu(zu8gwiYqDmLbjG25jH8DG zdnLoWW-0}N?7W*dhbs*eOHf@_9awvT3EaENXi25GFx5+eWdPXXTd%YGKH8KL-Hcpu;wXq=U-6V>?U4kSnosq;&y<4F zMwW6%qSZcPg31YYV*4AU zPwMReR?)x`e@sky>wL7B z*3^*s)|}uKE8$dz@fH78%8|3$2?rdvTHg6ZQWUwcgYrOktJrzJi;=I3a>J3z_M!T_ z%Q6OvDU24@i(8#9NZ&a9)jI!&}4oE`p`^ zy(_@mSLhQsljHSS_#AULifPZn;*QCAjkKZ4{j}u)0ADN)T_kiX0{)>X()zQf0c0-S z%)|!kOvF0aW0X{kMYHmVjT*lJC6UEC8qpZ;@bMs?2Ce!_V% zah9#hV_H-Cr3)NT8v4SOU0`|7c*MZ4%tun7My^CKH#n%JrrluL>z+%0qG;MSVpE&0 zFmoMuB}zd+W>-M*z+9;?d#ZA-GW_z}wyUP*^xZLDZjk1qvZ>Nzwl7Q0_Q%171;O^0 zxsM%1gfqGCH$IC!JMHo@&`raO)AZl$kfYB`3-fA6(tVw6;=`|b!yB=38mkycHnk?y zi}YatdALVsGcFuxtKuA1P0dKd6-3aUUZ!A}cCz#Lv8&FbYad>F5%+l~VZ+37`}LI9 zA?M>32j-!8k&1V$r@o0nFX#7Gty&a_=U74l9vP;`g5C?SWM#QtdBXpgQIxgCB77#- zSbh$FZQS53($d7(3z-@8gQri5x6Fdo3`E1dUrg);3 zD&BatcgTAzT`4_`n+j2?4D z10w!I^mTyhnrf{XmFG-luJ&y~Nxompkkq?0!S_?PM|IWftZN(2eD9GjbBA;H%s=Hl zFUo{Ru|>a_djL4jPoym&>!O*)C6R_W>&`)7_Ag7Cu)QrT>!gV2^MlyP$@;C1*2KRi zy@J(!Rj*ftIhEPji^b2`%{_LjUCmq+OfuI`N2G*1Z#;!(XD64gqzwB+{DV^4^HqI- zfXRaIzAt5Hq!+*r!p1Ar+tttta{}fBlc5+37A#hJpH0}QVZs(fqWuCnqG{fM@bnBJ z9_i!gX0&z_x30Fk&zT15b&*Q!&7)m)<8{uls`Adii9T~_DQ6C~2?vZcov@u=1L(qR zB_vf?`EmSH1*Xk8L=Eo-!%%J?9pk(#D?cayBf4NeJct}@hdIaTJ+CV`zLk1~jzaVf zR49yqP5%nf8t7I-={UEz=rg(N3~Vqg3XCfFxAvAEfP96|e{AOgz(6tl z8TBYGZ@oWoGOl)a5Jf_|?0B>`^7%$6Anha5>!yaB011kYZ$68NZ2Bhg&044mf;7wt z6;{!#xu$r1SJL^iSF5w`#k;>nVeePx=STe}!oJ{8T<2C!XGT09Lg9G^SX-l~>(sgJ z2k))^(NV&nGh+N0H+(w-ear-e+MA~buG>@8H8Z}BW4uS025lGm`Tv}IucKy|?=!|J zSLJ9sy-2qgjqS`~!5pU&wO&%Sq&Zy*D>=5AK{D(_I*-Hodhjv!+8)aVVngnr8?ct10=U9~qrWKrAaMf-P4 zDM781r0t~dS9j`^!nrT>b5id}y5whEy#;HYZ>`$ATv@PY`D0!PLpw_2lO;{$Fb0Zb%JXAvFNneSp>qSnk-V`E0339oY6o2yVZ^~S^nuF zn835y5-c=wa^}3SAPv{)*Hz)JY;->KXR9ug@rrp#bJ$2@XeE|I7A}QLnkVFIx;Vdg z>lVthxb`L)$r>=zg5o?2Sb1;I(JXu=QZK1kaUyupPf5F7X@!;tVp4L8T}wJ+@AbWg zhOqGMo!GHU8}XwY)K-bqIccDya3^)NziHY9_X2r0q+n*viz!0vAM=7H8zk~R-78W&2u1O7@Ur`$z+^gxhd4>qa zLHA5Azf?Q=zp0;J0V^#hce6;hNbPF^ePb>ZI@qGJ9!-j&LOa^``@n}$8B+C9h-^t@jn14%=WIB1`WaDb6KBI;7&~^(mRe@ z62@c_bm=g~h>NHm%&n37R=1uX2JmI}{V*Mm&r-T>VG>9cv_=X(frb||UEkCu+G_H? z{6IQ;;?;gLBZu6Xiu6n%jSUe2dj{IoocB0Ehrr&01f(SgkH@MQ9gm0RvA z@VkG`MhF{Qrg8~^-c^wRZ#6*D!;MQstBrt#=Wj|r5N3lV^vC=eLPx&Z+~mgPl}CL9 zURaneh@-NuXcg}0jWa}7F1?DUP%DlOSLRTPNx;`NEXaPqiA1)!AP(fMulF81zI`i~ zy8$%JT{I`GhOS>j{f05b z&`CVoPRi*6f`(lyjlGoOk^RnJQlZTpZdXC`7PuxK;l)7&bMs_}ld-FyQAVd7q}Yrp zDl_ZxB(1TWnF6}1XJ1ac5z{8i!Wcg107 z`8DrO+B!-RUD5x;Y<>9wFsK0;oa$QVI=OPz;6ym@Os4tuLu#iF(J27k6X-Z^?_Hdu z1%+==2#fCLTw`AZ1YPC73j*9W`o_CzD;zsQ&C3^_ph~SEJ6m0(r&1lhX)>Y%m9FDR zJ;Vk#{`rh^*Kym)%IdTWV=}5sxBmHw3l%C@^+f8IZ)JE?2h{)X?H1wFobVBp4-x+$mTN>k}i0)Nf3OS!P3`LLmG5UTH zy7=TDyE-T6?8wFa;4Q$37Jca`y}0xq1zfLmFoQN6xuY*I$OqoEK)rNqNY}AWO7HWf z2h)1=pgRZ1gmZ!Y%+~`#T)Mfp@47a1h!xRcj=Y%K`+EyS?H2;ZWb`yp`|&!+zB=#C z9qJFtZUTKU*Myut0Nz25>d`44QSdTlq(U|?*HEb1>xs*D%^PGG3PpI+@mNCV?-dw5`i(rQNDc*sw4(^lL)sdpc+g?~ zuCP0Y0bhR9cOJ-I9y)k`(*S8LCJ)FRi#wu0-+t?wMu&^Wk9*NQqB5UM+CKn}>rmih z0hYm32=dTy)K)IHqZd&)0$Ft(%e{>5N{0MThr2X%kJ1b#^}Po`5TQC^^ApusZ5DPF z$@{MRbBDki38hUGwmoVh?ez1#lEoPkT}l1`_z(n&S6np@IvAJcs}uG&I@%t(3-=3Y zNot5CMQ*2`w#U;fgzOTG+8NUB_YA=EQ{5`MHwvRJoB!;&a=n+b_&qn3EZlXa`9}r4@3uiX zZ@wL+LC((5=`QekfDf+pj5Oo`Eh5^k8tDNDI@7g!M_T05q^HI3qhaeJtH&4@H#LG*^*i zHXfs}A^(woMv6TE_|JJr^YgG`BfbZjdA?>4a#};89srvF)v17$#G* z@cS>k+fwJ-Y1xJ!cs~iPqkCqdN!K=Yct72}1PnC#q4R<2Xu>e0xW!0725zN__~vAX z35q<9%xAV1a{T8h0usePR_EjQ_Z-#IPV|pV4grMrzxEAMK|ia{TBG$lRrWVd5r26~ z{I)&MB(jtA-Obe<&PE3>%M}v|r6MsD7AdpE4HODOWr2Ad^EEEcx@KO9^49ld9$W;=?7f@Hk)A?f9t zv%JFqmhlaTib1I)DJ923E zf0prtnNc2i^Vn0MxM!I3%J7CTYWiDTx;3)!hOp+Q1dG+Z0e05uKD_!fV7rr0hOka5 z+^?o$p*8sd(BQ}leq*j2ZXFwviO6a`6T)-7bv!cfsSxHa78?N0#U!c^NgqOuXXA7@ z$#r~6F_d9@{B+u1w~K|I=#tUU&kVb;RCu?q--}&P%~v<-y^Sk&QWsMh{M1O{qerhZ zJJwpz^o3R}(Jz62(SuKKmdEL}eE&kmfQrhlu@7mnG|p;IK;{MIwS!|9Swk)%@k=Y5 ziz?5CB@EwT1*15*pD2Yynm@kM$rwlGX+bA({XUBl2O#H1mt0I=+Qej`yvQa0^Bs!P zvOu~yo(lrQ-lpi;U;R{>&ANGBDTRh_@d`_fihFdbWsHKxV_sqN2*FH`o`J|g$wo&i$opx=>frlr*rD@hs*W~$!m)}(*qD4q^@CU`% zvH9R~`oq=Xo};Z&<@nMm`;-p=q6?w`vp^?R?wIW~`!qiI|JA~m$3yk~{|{r!HrdK9 zOBkXsWFIO*F)^eNLYR?tEMsddNy$!OvSpbeYuU?|J-acM?2I*g*0Q|6x6kkQ&%KZP zxaV=tdENUu=f2MC`Fy@YdSsV7$?h7$umyUVu~_Y-G?x^1Z-s$pm}|wlPuPDd7P3xKGE+f|7c#J zuJ8^PIWi~P4p*M|%epza7V_JR;Zd}`^RfH(__foga}AglKV!>vCEcm+ZwcXa4+VRb z7mA!;aE~h1x=RdSyIL33EqZMu{G~AvI+hr<)I~(ma{;@)mZn{m9SZg}3W;+JL1c8^ zr}(N;X1PnzeENh8+gN#$0<9TXENLS4;bk49jw{-le`LP_c_RJbGoER|l!52P^Ku%` z?F587lgryO2qZ&(4xL~9_EuJXzryfZ-_1*xb-9U*i;SyM-dz!$I&Obe#O!L~#4h z2){1j=*FR6o@U+cz0uJa16uUj-%I%gdD~CzJ-w>e8Y9p$54ofgk#Xr(z zV6LV=p7r?5!Fy)Oo%=2v1px3dl7}VEFcyjc9E%!eS1aU95vB;g))W2Rk)pZ%Qg;3BpT6tFd_vj=!>_k=Ep+s za?ZsJE?2t3o)~O#;%7SiO>T8ZCnRzg^w^JqlXOx6t)<>w0d#ZdTUxN#As7E^^Ju#y zL)K+)V3#;ds}KL%djH&~|48vxlyN%Ao705*d>>*#Z(wCYb)B2#wvpz#jn?U<2(g{B z-<9a`M$h*==Uio(+x@bYhZipdFI?R>L~7`)_2Z_wG7R6oS_znVk{9Au8ND~o z=s5&Cw88ho+g*&5o_yE(QP@mZ{<$zr9Zmq|N}O3|IkENQT6wQn>>xdT^j_4|gj}d` zgvvnvrQ3B3_gMkil`|6I)7%dY^~x`}xX7&?YQ%((!w6%#N={6l zAYY+gKT10K@#B|dj?&q0$U{yOo-F8h@IQi|*}K{~@|UDc#10_EDm>|oN_@R_;gOii z9JEZ4Y$@A0(;#ZBc1_&<(9l1I64b_FERqmjs$YSqbw6bB7gO&w!iE1GD?Ba?b3S?w z+2Q@&rR)nt(~L8*z1<(>^73I5Q(d}i59$hSlnX3UUyUAy7Br?wCLjak=f{-0irGfX zh|@v_qaRSyY%qL<(m@HTihJ}bLiVc4_g{wgB;$4$sEXPbQ7$eAeWr~oEBM1R>4lUC zΠ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 literal 0 HcmV?d00001 diff --git a/diy/waveshare/README.md b/diy/waveshare/README.md index 9e8f4c306..a710b90a0 100644 --- a/diy/waveshare/README.md +++ b/diy/waveshare/README.md @@ -6,6 +6,19 @@ The device uses touchscreen navigation and the BOOT button for sleep/wake. Bluetooth is enabled by default but can be turned off with the NORADIO config script. +## S3 Touch LCD 3.5 + +Same navigation model as the Touch LCD 2 (virtual touch buttons, BOOT button +long-press for sleep/wake). The camera plugs into the onboard DVP FPC +connector (the -C version ships with an OV5640 fitted). Battery charging and +level reporting are handled by the onboard AXP2101 PMU; an optional 3.7V +lithium battery can be attached to the MX1.25 header (check the connector +polarity before plugging it in). A long press of the PWR button (4s) cuts +power; when off on battery, the PWR button powers the device back on. + +NOTE: the `3.5B` variant is a different device (QSPI display controller) and +is not supported. + ## Flashing To prepare the board for flashing, hold down the `BOOT` button and plug in the diff --git a/gitlab/diy_fw.yml b/gitlab/diy_fw.yml index 4b957a2ab..846a09f61 100644 --- a/gitlab/diy_fw.yml +++ b/gitlab/diy_fw.yml @@ -56,3 +56,6 @@ build_diy_diycam_tcameraplus: build_diy_display_waveshares3_touch_lcd2: extends: .build_diy_template + +build_diy_display_waveshares3_touch_lcd35: + extends: .build_diy_template diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 439c82664..5dc9ef5b6 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -116,6 +116,11 @@ menu "Blockstream Jade" select HAS_CAMERA select HAS_BATTERY select DISPLAY_TOUCHSCREEN + config BOARD_TYPE_WS_TOUCH_LCD35 + bool "Waveshare Touch LCD 3.5 (esp32s3)" + select HAS_CAMERA + select HAS_BATTERY + select DISPLAY_TOUCHSCREEN config BOARD_TYPE_QEMU bool "qemu display via websocket" config BOARD_TYPE_QEMU_LARGER @@ -143,7 +148,7 @@ menu "Blockstream Jade" config DISPLAY_TOUCHSCREEN bool "Set if touchscreen is available" - default y if BOARD_TYPE_M5_CORES3 || BOARD_TYPE_M5_CORE2 || BOARD_TYPE_TTGO_TWATCHS3 || BOARD_TYPE_WS_TOUCH_LCD2 + default y if BOARD_TYPE_M5_CORES3 || BOARD_TYPE_M5_CORE2 || BOARD_TYPE_TTGO_TWATCHS3 || BOARD_TYPE_WS_TOUCH_LCD2 || BOARD_TYPE_WS_TOUCH_LCD35 default n choice DISPLAY_TOUCHSCREEN_CONTROLLER @@ -166,12 +171,12 @@ menu "Blockstream Jade" config DISPLAY_SPI_CLOCK int "SPI Clock" - default 40000000 if BOARD_TYPE_JADE_V2_ANY || BOARD_TYPE_M5_CORES3 || BOARD_TYPE_TTGO_TWATCHS3 + default 40000000 if BOARD_TYPE_JADE_V2_ANY || BOARD_TYPE_M5_CORES3 || BOARD_TYPE_TTGO_TWATCHS3 || BOARD_TYPE_WS_TOUCH_LCD35 default 32000000 config DISPLAY_SPI_HOST int "SPI host" - default 2 if BOARD_TYPE_JADE_V2_ANY || BOARD_TYPE_M5_CORES3 + default 2 if BOARD_TYPE_JADE_V2_ANY || BOARD_TYPE_M5_CORES3 || BOARD_TYPE_WS_TOUCH_LCD35 default 1 config DISPLAY_INVERT @@ -179,7 +184,8 @@ menu "Blockstream Jade" default y if BOARD_TYPE_JADE_ANY || \ BOARD_TYPE_TTGO_TDISPLAY || BOARD_TYPE_M5_STICKC_PLUS || BOARD_TYPE_M5_STICKC_PLUS_2 || \ BOARD_TYPE_M5_FIRE || BOARD_TYPE_TTGO_TDISPLAYS3 || BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA || \ - BOARD_TYPE_M5_BLACK_GRAY || BOARD_TYPE_TTGO_TWATCHS3 || BOARD_TYPE_WS_TOUCH_LCD2 + BOARD_TYPE_M5_BLACK_GRAY || BOARD_TYPE_TTGO_TWATCHS3 || BOARD_TYPE_WS_TOUCH_LCD2 || \ + BOARD_TYPE_WS_TOUCH_LCD35 help Invert the colors in the dipslay. Some devices may have inverted colors depending on revision @@ -203,6 +209,7 @@ menu "Blockstream Jade" config I2C_SDA int "I2C SDA Pin" default 4 if BOARD_TYPE_JADE_V1_ANY + default 8 if BOARD_TYPE_WS_TOUCH_LCD35 default 12 if BOARD_TYPE_M5_CORES3 default 21 if BOARD_TYPE_M5_STICKC_PLUS || BOARD_TYPE_M5_CORE2 || BOARD_TYPE_M5_FIRE || BOARD_TYPE_M5_BLACK_GRAY default 42 if BOARD_TYPE_JADE_V2_ANY @@ -211,6 +218,7 @@ menu "Blockstream Jade" config I2C_SCL int "I2C SCL Pin" default 2 if BOARD_TYPE_JADE + default 7 if BOARD_TYPE_WS_TOUCH_LCD35 default 11 if BOARD_TYPE_M5_CORES3 || BOARD_TYPE_TTGO_TWATCHS3 default 14 if BOARD_TYPE_JADE_V1_1 default 22 if BOARD_TYPE_M5_STICKC_PLUS || BOARD_TYPE_M5_CORE2 || BOARD_TYPE_M5_FIRE || BOARD_TYPE_M5_BLACK_GRAY @@ -227,6 +235,7 @@ menu "Blockstream Jade" default 21 if BOARD_TYPE_M5_CORE2 default 39 if BOARD_TYPE_TTGO_TWATCHS3 default 48 if BOARD_TYPE_WS_TOUCH_LCD2 + default 8 if BOARD_TYPE_WS_TOUCH_LCD35 default -1 config I2C_TOUCH_SCL depends on DISPLAY_TOUCHSCREEN @@ -235,6 +244,7 @@ menu "Blockstream Jade" default 40 if BOARD_TYPE_TTGO_TWATCHS3 default 22 if BOARD_TYPE_M5_CORE2 default 47 if BOARD_TYPE_WS_TOUCH_LCD2 + default 7 if BOARD_TYPE_WS_TOUCH_LCD35 default -1 endmenu @@ -359,7 +369,7 @@ menu "Blockstream Jade" int "Camera pin D0" default 13 if BOARD_TYPE_JADE_V2 default 39 if BOARD_TYPE_M5_CORES3 - default 45 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA + default 45 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA || BOARD_TYPE_WS_TOUCH_LCD35 default 12 if BOARD_TYPE_WS_TOUCH_LCD2 default 32 config CAMERA_D1 @@ -368,6 +378,7 @@ menu "Blockstream Jade" default 40 if BOARD_TYPE_M5_CORES3 default 41 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA default 13 if BOARD_TYPE_WS_TOUCH_LCD2 + default 47 if BOARD_TYPE_WS_TOUCH_LCD35 default 35 config CAMERA_D2 int "Camera pin D2" @@ -375,12 +386,14 @@ menu "Blockstream Jade" default 41 if BOARD_TYPE_M5_CORES3 default 40 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA default 15 if BOARD_TYPE_WS_TOUCH_LCD2 + default 48 if BOARD_TYPE_WS_TOUCH_LCD35 default 34 config CAMERA_D3 int "Camera pin D3" default 14 if BOARD_TYPE_JADE_V2 default 42 if BOARD_TYPE_M5_CORES3 || BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA default 11 if BOARD_TYPE_WS_TOUCH_LCD2 + default 46 if BOARD_TYPE_WS_TOUCH_LCD35 default 5 config CAMERA_D4 int "Camera pin D4" @@ -389,6 +402,7 @@ menu "Blockstream Jade" default 12 if BOARD_TYPE_JADE_V2 default 1 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA default 14 if BOARD_TYPE_WS_TOUCH_LCD2 + default 42 if BOARD_TYPE_WS_TOUCH_LCD35 default -1 config CAMERA_D5 int "Camera pin D5" @@ -396,6 +410,7 @@ menu "Blockstream Jade" default 16 if BOARD_TYPE_M5_CORES3 default 3 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA default 10 if BOARD_TYPE_WS_TOUCH_LCD2 + default 40 if BOARD_TYPE_WS_TOUCH_LCD35 default 18 config CAMERA_D6 int "Camera pin D6" @@ -403,6 +418,7 @@ menu "Blockstream Jade" default 48 if BOARD_TYPE_M5_CORES3 default 10 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA default 7 if BOARD_TYPE_WS_TOUCH_LCD2 + default 39 if BOARD_TYPE_WS_TOUCH_LCD35 default 36 config CAMERA_D7 int "Camera pin D7" @@ -410,6 +426,7 @@ menu "Blockstream Jade" default 47 if BOARD_TYPE_M5_CORES3 default 4 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA default 2 if BOARD_TYPE_WS_TOUCH_LCD2 + default 21 if BOARD_TYPE_WS_TOUCH_LCD35 default 19 config CAMERA_XCLK @@ -417,10 +434,11 @@ menu "Blockstream Jade" default 8 if BOARD_TYPE_JADE_V2 || BOARD_TYPE_WS_TOUCH_LCD2 default -1 if BOARD_TYPE_M5_CORES3 default 11 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA + default 38 if BOARD_TYPE_WS_TOUCH_LCD35 default 27 config CAMERA_XCLK_FREQ int "Camera XCLK frequency" - default "16000000" if BOARD_TYPE_JADE_V2 || BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA || BOARD_TYPE_M5_CORES3 + default "16000000" if BOARD_TYPE_JADE_V2 || BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA || BOARD_TYPE_M5_CORES3 || BOARD_TYPE_WS_TOUCH_LCD35 default "20000000" config CAMERA_PCLK @@ -431,6 +449,7 @@ menu "Blockstream Jade" default 45 if BOARD_TYPE_M5_CORES3 default 2 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA default 9 if BOARD_TYPE_WS_TOUCH_LCD2 + default 41 if BOARD_TYPE_WS_TOUCH_LCD35 default -1 config CAMERA_VSYNC @@ -439,6 +458,7 @@ menu "Blockstream Jade" default 46 if BOARD_TYPE_M5_CORES3 default 7 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA default 6 if BOARD_TYPE_WS_TOUCH_LCD2 + default 17 if BOARD_TYPE_WS_TOUCH_LCD35 default 22 config CAMERA_HREF @@ -447,6 +467,7 @@ menu "Blockstream Jade" default 38 if BOARD_TYPE_M5_CORES3 default 15 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA default 4 if BOARD_TYPE_WS_TOUCH_LCD2 + default 18 if BOARD_TYPE_WS_TOUCH_LCD35 default 26 config CAMERA_SDA @@ -455,6 +476,7 @@ menu "Blockstream Jade" default 12 if BOARD_TYPE_M5_CORES3 default 5 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA default 21 if BOARD_TYPE_WS_TOUCH_LCD2 + default 8 if BOARD_TYPE_WS_TOUCH_LCD35 default 25 config CAMERA_SCL int "Camera SCL" @@ -462,6 +484,7 @@ menu "Blockstream Jade" default 11 if BOARD_TYPE_M5_CORES3 default 6 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA default 16 if BOARD_TYPE_WS_TOUCH_LCD2 + default 7 if BOARD_TYPE_WS_TOUCH_LCD35 default 23 config CAMERA_RESET @@ -485,7 +508,7 @@ menu "Blockstream Jade" int "Display width in pixels" range 0 640 default 320 if BOARD_TYPE_JADE_V2_ANY || BOARD_TYPE_QEMU_LARGER || BOARD_TYPE_M5_FIRE || BOARD_TYPE_M5_BLACK_GRAY || BOARD_TYPE_TTGO_TDISPLAYS3 || BOARD_TYPE_M5_CORES3 || BOARD_TYPE_M5_CORE2 - default 480 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA + default 480 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA || BOARD_TYPE_WS_TOUCH_LCD35 default 240 config DISPLAY_HEIGHT int "Display height in pixels" @@ -495,16 +518,16 @@ menu "Blockstream Jade" default 170 if BOARD_TYPE_JADE_V2_ANY || BOARD_TYPE_QEMU_LARGER || BOARD_TYPE_TTGO_TDISPLAYS3 default 200 if BOARD_TYPE_M5_CORES3 || BOARD_TYPE_TTGO_TWATCHS3 default 222 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA - default 280 if BOARD_TYPE_WS_TOUCH_LCD2 + default 280 if BOARD_TYPE_WS_TOUCH_LCD2 || BOARD_TYPE_WS_TOUCH_LCD35 default 135 config DCS_ADDRESS_MODE_BGR_SELECTED bool "BGR" - default y if BOARD_TYPE_M5_FIRE || BOARD_TYPE_M5_BLACK_GRAY || BOARD_TYPE_M5_CORES3 || BOARD_TYPE_M5_CORE2 || BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA + default y if BOARD_TYPE_M5_FIRE || BOARD_TYPE_M5_BLACK_GRAY || BOARD_TYPE_M5_CORES3 || BOARD_TYPE_M5_CORE2 || BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA || BOARD_TYPE_WS_TOUCH_LCD35 config DCS_ADDRESS_MODE_MIRROR_X_SELECTED bool "Mirror X" default y if BOARD_TYPE_JADE_ANY || \ BOARD_TYPE_TTGO_TDISPLAY || BOARD_TYPE_M5_STICKC_PLUS || BOARD_TYPE_M5_STICKC_PLUS_2 || BOARD_TYPE_TTGO_TDISPLAYS3 || BOARD_TYPE_TTGO_TWATCHS3 - default n if BOARD_TYPE_M5_FIRE || BOARD_TYPE_M5_BLACK_GRAY || BOARD_TYPE_M5_CORES3 || BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA || BOARD_TYPE_WS_TOUCH_LCD2 + default n if BOARD_TYPE_M5_FIRE || BOARD_TYPE_M5_BLACK_GRAY || BOARD_TYPE_M5_CORES3 || BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA || BOARD_TYPE_WS_TOUCH_LCD2 || BOARD_TYPE_WS_TOUCH_LCD35 config DCS_ADDRESS_MODE_MIRROR_Y_SELECTED bool "Mirror Y" default y if BOARD_TYPE_TTGO_TWATCHS3 @@ -512,19 +535,19 @@ menu "Blockstream Jade" config DCS_ADDRESS_MODE_SWAP_XY_SELECTED bool "Swap X and Y" default y if BOARD_TYPE_JADE_ANY - default y if BOARD_TYPE_TTGO_TDISPLAY || BOARD_TYPE_M5_STICKC_PLUS || BOARD_TYPE_M5_STICKC_PLUS_2 || BOARD_TYPE_TTGO_TDISPLAYS3 || BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA + default y if BOARD_TYPE_TTGO_TDISPLAY || BOARD_TYPE_M5_STICKC_PLUS || BOARD_TYPE_M5_STICKC_PLUS_2 || BOARD_TYPE_TTGO_TDISPLAYS3 || BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA || BOARD_TYPE_WS_TOUCH_LCD35 default n if BOARD_TYPE_M5_FIRE || BOARD_TYPE_M5_BLACK_GRAY || BOARD_TYPE_M5_CORES3 || BOARD_TYPE_TTGO_TWATCHS3 || BOARD_TYPE_WS_TOUCH_LCD2 config DISPLAY_OFFSET_X int "X offset" default 0 if BOARD_TYPE_JADE_V2_ANY default 0 if BOARD_TYPE_M5_FIRE || BOARD_TYPE_M5_BLACK_GRAY || BOARD_TYPE_TTGO_TDISPLAYS3 || BOARD_TYPE_TTGO_TWATCHS3 - default 0 if BOARD_TYPE_M5_CORES3 || BOARD_TYPE_M5_CORE2 || BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA || BOARD_TYPE_WS_TOUCH_LCD2 + default 0 if BOARD_TYPE_M5_CORES3 || BOARD_TYPE_M5_CORE2 || BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA || BOARD_TYPE_WS_TOUCH_LCD2 || BOARD_TYPE_WS_TOUCH_LCD35 default 40 help Adjust when using small display and image is not centered. config DISPLAY_OFFSET_Y int "Y offset" - default 0 if BOARD_TYPE_M5_FIRE || BOARD_TYPE_M5_BLACK_GRAY || BOARD_TYPE_M5_CORES3 || BOARD_TYPE_M5_CORE2 || BOARD_TYPE_WS_TOUCH_LCD2 + default 0 if BOARD_TYPE_M5_FIRE || BOARD_TYPE_M5_BLACK_GRAY || BOARD_TYPE_M5_CORES3 || BOARD_TYPE_M5_CORE2 || BOARD_TYPE_WS_TOUCH_LCD2 || BOARD_TYPE_WS_TOUCH_LCD35 default 35 if BOARD_TYPE_JADE_V2_ANY || BOARD_TYPE_TTGO_TDISPLAYS3 default 49 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA default 80 if BOARD_TYPE_TTGO_TWATCHS3 @@ -539,6 +562,7 @@ menu "Blockstream Jade" default 38 if BOARD_TYPE_M5_CORE2 default 8 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA default 40 if BOARD_TYPE_WS_TOUCH_LCD2 + default 2 if BOARD_TYPE_WS_TOUCH_LCD35 default -1 config DISPLAY_PIN_MOSI int "MOSI pin number" @@ -551,6 +575,7 @@ menu "Blockstream Jade" default 17 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA default 13 if BOARD_TYPE_TTGO_TWATCHS3 default 38 if BOARD_TYPE_WS_TOUCH_LCD2 + default 1 if BOARD_TYPE_WS_TOUCH_LCD35 default -1 config DISPLAY_PIN_CLK int "CLK pin number" @@ -561,6 +586,7 @@ menu "Blockstream Jade" default 18 if BOARD_TYPE_M5_FIRE || BOARD_TYPE_M5_BLACK_GRAY || BOARD_TYPE_TTGO_TDISPLAY || BOARD_TYPE_M5_CORE2 || BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA || BOARD_TYPE_TTGO_TWATCHS3 default 36 if BOARD_TYPE_M5_CORES3 default 39 if BOARD_TYPE_WS_TOUCH_LCD2 + default 5 if BOARD_TYPE_WS_TOUCH_LCD35 default -1 config DISPLAY_PIN_CS int "CS pin number" @@ -588,6 +614,7 @@ menu "Blockstream Jade" default 9 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA default 38 if BOARD_TYPE_TTGO_TWATCHS3 default 42 if BOARD_TYPE_WS_TOUCH_LCD2 + default 3 if BOARD_TYPE_WS_TOUCH_LCD35 default -1 config DISPLAY_PIN_RST int "RST pin number" @@ -607,6 +634,7 @@ menu "Blockstream Jade" default 48 if BOARD_TYPE_TTGO_TDISPLAYS3PROCAMERA default 45 if BOARD_TYPE_TTGO_TWATCHS3 default 1 if BOARD_TYPE_WS_TOUCH_LCD2 + default 6 if BOARD_TYPE_WS_TOUCH_LCD35 default -1 config PIN_NUM_DC int "DC pin number" diff --git a/main/camera.c b/main/camera.c index 5835dddb6..120e94307 100644 --- a/main/camera.c +++ b/main/camera.c @@ -281,10 +281,19 @@ static void jade_camera_init(void) .jpeg_quality = 0 }; +#if defined(CONFIG_DISPLAY_TOUCHSCREEN) + // On some boards the touchscreen i2c bus uses the same pins as the camera's + // sccb - stop it so the camera can drive them (it is restarted below), + // otherwise the sccb cannot claim the pins and the sensor is never found + touchscreen_deinit(); +#endif const esp_err_t err = esp_camera_init(&camera_config); JADE_LOGI("Camera init done"); if (err != ESP_OK) { JADE_LOGE("Camera init failed with error 0x%x", err); +#if defined(CONFIG_DISPLAY_TOUCHSCREEN) + touchscreen_init(); +#endif camera_post_exit_event_and_await_death(); } @@ -321,7 +330,7 @@ static void jade_camera_init(void) } #endif // !defined(CONFIG_ETH_USE_OPENETH) && defined(ESP_PLATFORM) #if defined(CONFIG_DISPLAY_TOUCHSCREEN) - touchscreen_deinit(); + // Restart the touchscreen stopped before esp_camera_init() above touchscreen_init(); #endif } diff --git a/main/display.c b/main/display.c index 591255611..ebab1df7b 100644 --- a/main/display.c +++ b/main/display.c @@ -186,7 +186,7 @@ void display_fill_rect(int x, int y, int w, int h, color_t color) JADE_LOGE( "display_fill_rect called with bad params (ignored) x %d y %d w %d h %d color %u\n", x, y, w, h, color); #if !defined(CONFIG_BOARD_TYPE_M5_CORES3) && !defined(CONFIG_BOARD_TYPE_TTGO_TWATCHS3) \ - && !defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) + && !defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) && !defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD35) return; #endif } @@ -253,7 +253,7 @@ void display_init(TaskHandle_t* gui_h) display_hw_init(gui_h); #if defined(CONFIG_BOARD_TYPE_TTGO_TWATCHS3) || defined(CONFIG_BOARD_TYPE_M5_CORES3) \ - || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) + || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD35) #define TOUCH_BUTTON_AREA 40 #define TOUCH_BUTTON_MARGIN 5 #define TOUCH_BUTTON_WIDTH 40 diff --git a/main/display_hw.c b/main/display_hw.c index 79629afc3..3afc0d533 100644 --- a/main/display_hw.c +++ b/main/display_hw.c @@ -87,7 +87,8 @@ static void esp_lcd_init(void* _ignored) esp_lcd_panel_io_handle_t io_handle = NULL; #if CONFIG_DISPLAY_PIN_BL != -1 && !defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) \ - && !defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAY) && !defined(CONFIG_BOARD_TYPE_M5_STICKC_PLUS_2) + && !defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD35) && !defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAY) \ + && !defined(CONFIG_BOARD_TYPE_M5_STICKC_PLUS_2) gpio_config_t bk_gpio_config = { .mode = GPIO_MODE_OUTPUT, .pin_bit_mask = 1ULL << CONFIG_DISPLAY_PIN_BL }; ESP_ERROR_CHECK(gpio_config(&bk_gpio_config)); ESP_ERROR_CHECK(gpio_set_level(CONFIG_DISPLAY_PIN_BL, 0)); @@ -285,7 +286,7 @@ inline void display_hw_draw_bitmap(int x, int y, int w, int h, const uint16_t* c const int calculatedx = x - CONFIG_DISPLAY_OFFSET_X; const int calculatedy = y - CONFIG_DISPLAY_OFFSET_Y; #if (defined(CONFIG_BOARD_TYPE_M5_CORES3) || defined(CONFIG_BOARD_TYPE_TTGO_TWATCHS3) \ - || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2)) \ + || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD35)) \ && defined(CONFIG_DISPLAY_FULL_FRAME_BUFFER) /* this is required for the virtual buttons */ if (calculatedy >= CONFIG_DISPLAY_HEIGHT) { diff --git a/main/input/touchscreen.inc b/main/input/touchscreen.inc index 4f1540f11..5a8e35481 100644 --- a/main/input/touchscreen.inc +++ b/main/input/touchscreen.inc @@ -24,14 +24,29 @@ static volatile bool shutdown_finished = false; esp_err_t _i2c_init_master(i2c_port_t port_num, int sda_io_num, int scl_io_num, i2c_master_bus_handle_t* handle); esp_err_t _i2c_deinit(i2c_master_bus_handle_t handle); +#ifdef CONFIG_BOARD_TYPE_WS_TOUCH_LCD35 +// The pmu shares these i2c pins and has no bus of its own - lend it ours for +// as long as it exists (see power/wslcdtouch35.inc) +void power_i2c_bus_attach(i2c_master_bus_handle_t bus); +void power_i2c_bus_detach(void); +#endif static void touchscreen_task(void* ignored) { esp_lcd_touch_handle_t ret_touch = NULL; // FIXME: check mirror flags? const esp_lcd_touch_config_t tp_cfg = { +#ifdef CONFIG_BOARD_TYPE_WS_TOUCH_LCD35 + // The panel is portrait-native (320x480) while the display is used in + // landscape. NOTE: esp_lcd_touch mirrors *before* swapping the axes, + // so these bounds are the panel's own axes, not the display's - and + // they include the virtual button strip below the gui area + .x_max = CONFIG_DISPLAY_HEIGHT + CONFIG_DISPLAY_OFFSET_Y + 40, + .y_max = CONFIG_DISPLAY_WIDTH + CONFIG_DISPLAY_OFFSET_X, +#else .x_max = CONFIG_DISPLAY_WIDTH + CONFIG_DISPLAY_OFFSET_X, .y_max = CONFIG_DISPLAY_HEIGHT + CONFIG_DISPLAY_OFFSET_Y, +#endif .rst_gpio_num = GPIO_NUM_NC, .int_gpio_num = GPIO_NUM_NC, .levels = { @@ -39,9 +54,17 @@ static void touchscreen_task(void* ignored) .interrupt = 0, }, .flags = { +#ifdef CONFIG_BOARD_TYPE_WS_TOUCH_LCD35 + // Verified on the hardware: display_x = panel_y and + // display_y = panel_max_x - panel_x + .swap_xy = 1, + .mirror_x = 1, + .mirror_y = 0, +#else .swap_xy = 0, .mirror_x = 0, .mirror_y = 0, +#endif }, }; @@ -49,6 +72,9 @@ static void touchscreen_task(void* ignored) ESP_ERROR_CHECK(_i2c_init_master( CONFIG_DISPLAY_TOUCHSCREEN_I2C, CONFIG_I2C_TOUCH_SDA, CONFIG_I2C_TOUCH_SCL, &touch_i2c_handle)); JADE_ASSERT(touch_i2c_handle); +#ifdef CONFIG_BOARD_TYPE_WS_TOUCH_LCD35 + power_i2c_bus_attach(touch_i2c_handle); +#endif esp_lcd_panel_io_handle_t tp_io_handle = NULL; esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_CONFIG(); @@ -92,6 +118,9 @@ static void touchscreen_task(void* ignored) vTaskDelay(20 / portTICK_PERIOD_MS); } // Since IDF 5.5 the i2c bus cannot be deleted while devices are still attached +#ifdef CONFIG_BOARD_TYPE_WS_TOUCH_LCD35 + power_i2c_bus_detach(); +#endif ESP_ERROR_CHECK(esp_lcd_touch_del(ret_touch)); ESP_ERROR_CHECK(esp_lcd_panel_io_del(tp_io_handle)); ESP_ERROR_CHECK(_i2c_deinit(touch_i2c_handle)); diff --git a/main/power.c b/main/power.c index 1dd7624ba..d6f8d5b6a 100644 --- a/main/power.c +++ b/main/power.c @@ -27,6 +27,9 @@ #include "power/ip5306.inc" #elif defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) #include "power/wslcdtouch2.inc" +#elif defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD35) +// Waveshare Touch LCD 3.5 has AXP2101 +#include "power/wslcdtouch35.inc" #elif defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAYS3) // T-Display S3 can read battery level but lacks hardware to read charging status #include "power/tdisplays3.inc" diff --git a/main/power/wslcdtouch35.inc b/main/power/wslcdtouch35.inc new file mode 100644 index 000000000..37b975557 --- /dev/null +++ b/main/power/wslcdtouch35.inc @@ -0,0 +1,286 @@ +// Waveshare S3 Touch LCD 3.5 implementation +// Has an AXP2101 PMU and a TCA9554 io-expander which share the i2c pins with +// both the touch controller and the camera's sccb. +// As with the M5CoreS3, the bus is only held while configuring the pmu and is +// released immediately afterwards - otherwise the camera cannot drive the +// shared pins (esp_camera_init() blocks forever). +// +#include "power/i2c.inc" +#include +#include +#include +#include + +#include "button_gpio.h" +#include "iot_button.h" +#include "power.h" +#include "soc/gpio_num.h" +#include "tusb.h" + +#define AXP2101_ADDR 0x34 +#define TCA9554_ADDR 0x20 + +#define LCD_BL_LEDC_TIMER LEDC_TIMER_0 +#define LCD_BL_LEDC_MODE LEDC_LOW_SPEED_MODE + +#define LCD_BL_LEDC_CHANNEL LEDC_CHANNEL_0 +#define LCD_BL_LEDC_DUTY_RES LEDC_TIMER_10_BIT +#define LCD_BL_LEDC_DUTY (1024) +#define LCD_BL_LEDC_FREQUENCY (10000) + +static i2c_master_dev_handle_t axp2101 = NULL; +static i2c_master_dev_handle_t tca9554 = NULL; + +// The pmu shares the i2c pins with the touchscreen and the camera, so it +// cannot own a bus of its own. Instead the touchscreen code hands us its bus +// while it exists (see power_i2c_bus_attach()/power_i2c_bus_detach()) and we +// attach the pmu to it - the i2c driver serialises access between devices. +// While the bus is away (ie. the camera is in use) we report the last reading. +static SemaphoreHandle_t pmu_mutex = NULL; +static i2c_master_dev_handle_t axp2101_shared = NULL; +static uint16_t cached_vbat = 0; +static uint8_t cached_soc = 0; +static bool cached_charging = false; +static bool cached_vbus = true; + +void power_i2c_bus_attach(i2c_master_bus_handle_t bus) +{ + JADE_ASSERT(bus); + JADE_ASSERT(pmu_mutex); + xSemaphoreTake(pmu_mutex, portMAX_DELAY); + if (!axp2101_shared) { + const i2c_device_config_t cfg = { .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = AXP2101_ADDR, + .scl_speed_hz = CONFIG_I2C_MASTER_CLK }; + I2C_LOG_ANY_ERROR(i2c_master_bus_add_device(bus, &cfg, &axp2101_shared)); + } + xSemaphoreGive(pmu_mutex); +} + +void power_i2c_bus_detach(void) +{ + JADE_ASSERT(pmu_mutex); + xSemaphoreTake(pmu_mutex, portMAX_DELAY); + if (axp2101_shared) { + I2C_LOG_ANY_ERROR(i2c_master_bus_rm_device(axp2101_shared)); + axp2101_shared = NULL; + } + xSemaphoreGive(pmu_mutex); +} + +// Returns false (leaving 'out' untouched) if the bus is not currently ours +static bool pmu_read(const uint8_t reg, uint8_t* out, const size_t len) +{ + JADE_ASSERT(out); + if (!pmu_mutex) { + return false; + } + bool ok = false; + xSemaphoreTake(pmu_mutex, portMAX_DELAY); + if (axp2101_shared) { + ok = _power_master_read_slave(axp2101_shared, reg, out, len) == ESP_OK; + } + xSemaphoreGive(pmu_mutex); + return ok; +} + +static void tca9554_init(void) +{ + // Set expander pins P0 and P1 as outputs, drive both low, then release P1 + // (display/peripheral power sequencing - same sequence the vendor demo uses) + _power_write_command(tca9554, 0x03, 0xFC); + vTaskDelay(100 / portTICK_PERIOD_MS); + _power_write_command(tca9554, 0x01, 0x00); + vTaskDelay(100 / portTICK_PERIOD_MS); + _power_write_command(tca9554, 0x01, 0x02); +} + +static void axp2101_init(void) +{ + _power_write_command(axp2101, 0x22, 0x06); // PWRON > OFFLEVEL as POWEROFF source + _power_write_command(axp2101, 0x27, 0x10); // hold 4s to power off via PWRON + _power_write_command(axp2101, 0x80, 0x01); // disable all DCs except DC1 + _power_write_command(axp2101, 0x90, 0x00); // disable all LDOs + _power_write_command(axp2101, 0x91, 0x00); // disable all LDOs + _power_write_command(axp2101, 0x82, 0x12); // DC1 3.3v + _power_write_command(axp2101, 0x92, 0x1C); // ALDO1 3.3v + _power_write_command(axp2101, 0x96, 0x0A); // BLDO1 1.5v (camera DVDD) + _power_write_command(axp2101, 0x97, 0x17); // BLDO2 2.8v (camera AVDD) + _power_write_command(axp2101, 0x90, 0x31); // enable ALDO1, BLDO1, BLDO2 + _power_write_command(axp2101, 0x64, 0x02); // charge voltage 4.1v + _power_write_command(axp2101, 0x61, 0x02); // precharge current 50ma + _power_write_command(axp2101, 0x62, 0x0A); // charge current 400ma + _power_write_command(axp2101, 0x63, 0x01); // charge termination current 25ma + _power_write_command(axp2101, 0x30, 0x1D); // enable VBAT/VBUS/VSYS/TDIE adc +} + +static void button_shutdown(void* arg, void* ctx) +{ + power_backlight_off(); + while (gpio_get_level(GPIO_NUM_0) == 0) { + vTaskDelay(pdMS_TO_TICKS(10)); + } + power_shutdown(); +} + +static esp_err_t boot_button_init(void) +{ + const button_config_t btn_cfg = { + .long_press_time = CONFIG_BUTTON_LONG_PRESS_TIME_MS, + .short_press_time = CONFIG_BUTTON_SHORT_PRESS_TIME_MS, + }; + const button_gpio_config_t btn_gpio_cfg = { + .gpio_num = 0, + .active_level = 0, + }; + button_handle_t btn_handle = NULL; + ESP_ERROR_CHECK(iot_button_new_gpio_device(&btn_cfg, &btn_gpio_cfg, &btn_handle)); + ESP_ERROR_CHECK(iot_button_register_cb(btn_handle, BUTTON_LONG_PRESS_HOLD, NULL, button_shutdown, NULL)); + esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL); + gpio_hold_en(GPIO_NUM_0); + gpio_deep_sleep_hold_en(); + esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0); + return ESP_OK; +} + +static esp_err_t brightness_init(void) +{ + gpio_reset_pin(CONFIG_DISPLAY_PIN_BL); + gpio_set_direction(CONFIG_DISPLAY_PIN_BL, GPIO_MODE_OUTPUT); + gpio_set_level(CONFIG_DISPLAY_PIN_BL, 1); + ledc_timer_config_t ledc_timer = { .speed_mode = LCD_BL_LEDC_MODE, + .timer_num = LCD_BL_LEDC_TIMER, + .duty_resolution = LCD_BL_LEDC_DUTY_RES, + .freq_hz = LCD_BL_LEDC_FREQUENCY, + .clk_cfg = LEDC_AUTO_CLK }; + ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); + ledc_channel_config_t ledc_channel = { .speed_mode = LCD_BL_LEDC_MODE, + .channel = LCD_BL_LEDC_CHANNEL, + .timer_sel = LCD_BL_LEDC_TIMER, + .intr_type = LEDC_INTR_DISABLE, + .gpio_num = CONFIG_DISPLAY_PIN_BL, + .duty = 0, + .hpoint = 0 }; + ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); + return ESP_OK; +} + +esp_err_t power_init(void) +{ + JADE_ASSERT(!pmu_mutex); + pmu_mutex = xSemaphoreCreateMutex(); + JADE_ASSERT(pmu_mutex); + + I2C_CHECK_RET(_power_i2c_init()); + I2C_CHECK_RET(_power_i2c_attach_device(TCA9554_ADDR, &tca9554)); + I2C_CHECK_RET(_power_i2c_attach_device(AXP2101_ADDR, &axp2101)); + + tca9554_init(); + axp2101_init(); + + vTaskDelay(100 / portTICK_PERIOD_MS); + // Release the bus so the touchscreen and the camera can drive these pins. + // Since IDF 5.5 the i2c bus cannot be deleted while devices are attached. + I2C_CHECK_RET(i2c_master_bus_rm_device(tca9554)); + I2C_CHECK_RET(i2c_master_bus_rm_device(axp2101)); + I2C_CHECK_RET(_i2c_deinit(NULL)); + + // Use the BOOT button as a sleep/wakeup button + ESP_ERROR_CHECK(boot_button_init()); + // Initialise backlight brightness + ESP_ERROR_CHECK(brightness_init()); + + return ESP_OK; +} + +esp_err_t power_shutdown(void) +{ + // NOTE: cannot use the pmu soft-poweroff as the i2c bus has been released + esp_deep_sleep_start(); +} + +esp_err_t power_screen_on(void) { return ESP_OK; } +esp_err_t power_screen_off(void) { return ESP_OK; } + +esp_err_t power_backlight_on(uint8_t brightness) +{ + if (brightness < BACKLIGHT_MIN) { + brightness = BACKLIGHT_MIN; + } else if (brightness > BACKLIGHT_MAX) { + brightness = BACKLIGHT_MAX; + } + const uint32_t duty = (brightness * (LCD_BL_LEDC_DUTY - 1)) / 5; + ESP_ERROR_CHECK(ledc_set_duty(LCD_BL_LEDC_MODE, LCD_BL_LEDC_CHANNEL, duty)); + ESP_ERROR_CHECK(ledc_update_duty(LCD_BL_LEDC_MODE, LCD_BL_LEDC_CHANNEL)); + return ESP_OK; +} + +esp_err_t power_backlight_off(void) +{ + ESP_ERROR_CHECK(ledc_set_duty(LCD_BL_LEDC_MODE, LCD_BL_LEDC_CHANNEL, 0)); + ESP_ERROR_CHECK(ledc_update_duty(LCD_BL_LEDC_MODE, LCD_BL_LEDC_CHANNEL)); + return ESP_OK; +} + +// Camera rails (BLDO1/BLDO2) are left enabled from power_init() +esp_err_t power_camera_on(void) { return ESP_OK; } +esp_err_t power_camera_off(void) { return ESP_OK; } + +uint16_t power_get_vbat(void) +{ + // 14-bit adc value split over two registers, lsb is 1mv + uint8_t buf[2] = { 0, 0 }; + if (pmu_read(0x34, buf, sizeof(buf))) { + cached_vbat = ((buf[0] & 0x3F) << 8) | buf[1]; + } + return cached_vbat; +} + +uint8_t power_get_battery_status(void) +{ + uint8_t soc = 0; + if (pmu_read(0xA4, &soc, sizeof(soc))) { + cached_soc = soc; + } + if (cached_soc > 90) { + return 5; + } else if (cached_soc > 70) { + return 4; + } else if (cached_soc > 50) { + return 3; + } else if (cached_soc > 30) { + return 2; + } else if (cached_soc > 10) { + return 1; + } + return 0; +} + +bool power_get_battery_charging(void) +{ + uint8_t status = 0; + if (pmu_read(0x01, &status, sizeof(status))) { + // status2 bits [6:5]: 01 = charging + cached_charging = ((status >> 5) & 0x03) == 0x01; + } + return cached_charging; +} + +uint16_t power_get_ibat_charge(void) { return 0; } +uint16_t power_get_ibat_discharge(void) { return 0; } +uint16_t power_get_vusb(void) { return 0; } +uint16_t power_get_iusb(void) { return 0; } +uint16_t power_get_temp(void) { return 0; } + +void disable_usb_host(void) {} +void enable_usb_host(void) {} + +bool usb_is_powered(void) +{ + uint8_t status = 0; + if (pmu_read(0x00, &status, sizeof(status))) { + // status1 bit 5: vbus present and good + cached_vbus = (status & (1 << 5)) != 0; + } + return cached_vbus; +} diff --git a/main/process/dashboard.c b/main/process/dashboard.c index 56cba90dd..fb1e3c6a3 100644 --- a/main/process/dashboard.c +++ b/main/process/dashboard.c @@ -1703,8 +1703,8 @@ static void handle_view_otps(void) // NOTE: Only boards listed here have brightness controls #if defined(CONFIG_BOARD_TYPE_JADE_V1_1) || defined(CONFIG_BOARD_TYPE_JADE_V2_ANY) \ - || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) || defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAY) \ - || defined(CONFIG_BOARD_TYPE_M5_STICKC_PLUS_2) + || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD35) \ + || defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAY) || defined(CONFIG_BOARD_TYPE_M5_STICKC_PLUS_2) static void handle_screen_brightness(void) { static const char* LABELS[] = { "Min(1)", "Low(2)", "Medium(3)", "High(4)", "Max(5)" }; @@ -2061,6 +2061,12 @@ static void handle_display_battery_volts(void) const int ret = snprintf(power_status, sizeof(power_status), "%umv", vbat); JADE_ASSERT(ret > 0 && ret < sizeof(power_status)); } +#elif defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD35) + const uint16_t vbat = power_get_vbat(); // pmu adc value already in mv + if (vbat > 0) { + const int ret = snprintf(power_status, sizeof(power_status), "%umv", vbat); + JADE_ASSERT(ret > 0 && ret < sizeof(power_status)); + } #elif defined(CONFIG_HAS_IP5306) const float approx_voltage = power_get_vbat() / 1000.0; const int ret = snprintf(power_status, sizeof(power_status), "Approx %.1fv", approx_voltage); @@ -2291,8 +2297,8 @@ static void handle_settings(const bool startup_menu) // NOTE: Only boards listed here have brightness controls #if defined(CONFIG_BOARD_TYPE_JADE_V1_1) || defined(CONFIG_BOARD_TYPE_JADE_V2_ANY) \ - || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) || defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAY) \ - || defined(CONFIG_BOARD_TYPE_M5_STICKC_PLUS_2) + || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD35) \ + || defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAY) || defined(CONFIG_BOARD_TYPE_M5_STICKC_PLUS_2) case BTN_SETTINGS_DISPLAY_BRIGHTNESS: handle_screen_brightness(); break; diff --git a/main/process/ota_defines.h b/main/process/ota_defines.h index bb1541232..86f12fdff 100644 --- a/main/process/ota_defines.h +++ b/main/process/ota_defines.h @@ -47,6 +47,8 @@ #define JADE_OTA_BOARD_TYPE "TTGO_TDISPLAYS3PROCAMERA" #elif defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) #define JADE_OTA_BOARD_TYPE "WAVESHARE_TOUCH_LCD2" +#elif defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD35) +#define JADE_OTA_BOARD_TYPE "WAVESHARE_TOUCH_LCD35" #elif defined(CONFIG_BOARD_TYPE_QEMU) || defined(CONFIG_BOARD_TYPE_QEMU_LARGER) #define JADE_OTA_BOARD_TYPE "QEMU" #else diff --git a/main/ui/dashboard.c b/main/ui/dashboard.c index 2001468c3..473dd132c 100644 --- a/main/ui/dashboard.c +++ b/main/ui/dashboard.c @@ -395,7 +395,8 @@ gui_activity_t* make_display_settings_activity(void) // NOTE: Only boards listed here have brightness controls // NOTE: Jade v1.1's do not support Flip Orientation because of issues with screen offsets #if defined(CONFIG_BOARD_TYPE_JADE_V2_ANY) || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) \ - || defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAY) || defined(CONFIG_BOARD_TYPE_M5_STICKC_PLUS_2) + || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD35) || defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAY) \ + || defined(CONFIG_BOARD_TYPE_M5_STICKC_PLUS_2) btn_data_t menubtns[] = { { .txt = "Display Brightness", .font = GUI_DEFAULT_FONT, .ev_id = BTN_SETTINGS_DISPLAY_BRIGHTNESS }, { .txt = "Flip Orientation", .font = GUI_DEFAULT_FONT, .ev_id = BTN_SETTINGS_DISPLAY_ORIENTATION },