From 9a7824358ac14c4cd55981eacfdf76bfe38b7fff Mon Sep 17 00:00:00 2001 From: pcgaldo Date: Mon, 3 Nov 2025 17:25:49 +0100 Subject: [PATCH] Enviar arquivos para "/" --- INA226.rar | Bin 0 -> 28378 bytes Intrucciones de uso y técnicas.txt | 124 ++ Repromod_Tester_v1.ino | 2694 ++++++++++++++++++++++++++++ repromod tester.stl | Bin 0 -> 80384 bytes 4 files changed, 2818 insertions(+) create mode 100644 INA226.rar create mode 100644 Intrucciones de uso y técnicas.txt create mode 100644 Repromod_Tester_v1.ino create mode 100644 repromod tester.stl diff --git a/INA226.rar b/INA226.rar new file mode 100644 index 0000000000000000000000000000000000000000..2748fc58b62eb249442958d87fc813e0366f8ade GIT binary patch literal 28378 zcmagmV|XQ9+Ar|fPCB-2+qP}nwr$%T+eXK>?T+ov$;|W2oHOrr&3nG=y=zr{tKYi+ zbyuz0iUy7ZnBaf_8~|uRFo2*C0N{Xt@0$Vs00?4uk90u5&_%Ey2e|mE_)skVzyQb+ zvI2~ZtaL)+0@{OM3$uu#KT%s4sqUrf<6r_*U;mnkBC`!X!3T~gV6Wnvw zVyf>%$C<|?B5pMIN=I~w3xkrMZJb}G z66Iu+7Fo&14@s4IV{u3=)5yDL8+dn}ns`1TqYZXVhMeH;K@?yL8jn{mA*I=!>_IOh zF)-;=)?%inpm%28^UC!=t^sgD()AIRn&7p6KMrk4FjM z9qiXX_vl<2Z2&i)gRIBq3HfpXO`vLmO9Tb2IC`sTW?Th6b$2F=Mv>?`j(8YwKb`NaD>TVY75*R0I0$R~$cWa#*U zP&N?~t$^V5m<|T-HpWEp zVBAV^J(6Vy@UKa?VL!XvjWm>T(@jBtM_Ban)Bpb2mqKuy%m0T})H5u^&%hr}X0rll zBYL+$@UX+i?_FzAWwxc7WskGJ6j5Gr+TxLvD4Cdwn#j7)t8fkgX0*nqnBaF|ZZkGy zksVw=`^~xp3iHgbDnF=)UX@-fEHt>95`DoBh!FF{z|L7(dz(LK&vk~&D}MReEOhN; z5OSNzk6ZnRsvfA$Hd!W>lVsFQ>0X`^nDR~0o?x|Ecvc2HF}?Pf*_2sR8+_L)Mg^U2 z)4SK_J7EI}nSGHqiLf6yQ@^Y5TRU(ZuE(#zo~^7fAE?MTetP+NWGB5KpGZj4#*YE% z7FfNKDfUqQ9A$?}{XM$%0jm%@l7P+i7BYYkLRmiDgDL#05-N+Ro-KQmwDnI_J3y!Y zGpG7Awn?{gLmKd#yErYd&Q=lB<9c2@9^>=}y!O!i5V?B?{gczZi%+x+$TfjUJq!UkvNv>KWq z%ra`phy#Lh>yZmHIXT&eaA4_UML+&L3aFg49w5e?6mjvpkIdMpRY@%vxufJVI;9{w zD^qUuLn5v$2Q#j2Q|_+d&s1~&>XZi>+gUBIm|pa=oSNIt_WFCbth1%C9Ho5e9+WF5 zGarEp3LG2jBtK+W;IGD&(6tgOTD~j;ce5omY7qxYI?Y_iP!-H~D7NHKM+@=-*`4RYl&#(3k<^;Z47prci}m_-Bv8Ftk|=IF4(r|OAG8^GI` zYi$KB+lWkG4Kq$`IkG1{nBOf%Y{Cy_R2Q^{qUj8$vYDJub__CM6q|*)7AMIh?I0OO zpxDYQ_C8BQk_9}`?y-Ubo$WF@8+%KG)Os39O`U$$N-C^Nkaw|bGhOy+@M@^@<02#T zva%TZQi}|7Y8J;&B+YW7JIbkBhymQO5q^)kRo?An195$XZER|FptK+ci7Y2sziUn7HjO8BinnszJ!d@=8Frw70yj{(q zT`}DWMn}{p1h%D(Fl_*E3{;Fre8<`)2*?o>e&>K;;diXj{2gm1?glpY)+SDLe{Jg- zI64}5{Kp2Zg{|Fp;5h+^{4?+--YkIljhPvlAM{18+*K{QSW+Oq7;SGL*%3bERWKQ| z_^_Fi&w`9`mtm8)d4O8t+~;MwMqVI*$KQW^F;2DwEIrlsYQYruzvqS@C52F1=hp4ucz zF$nWIpJL0g-JXd}3A$rG)VBlKMc2XEVY%gldV7CcGPH=PGdy^&7f+`HYla0Y02L2Y z$QB~Oncpzp7q2@McP8U)5pMdv=l3rpl%I2zi!c#gvIqPJUUKoqeGiV!Zg_{2Ax`+>WX10GyXRqjv5j4~%d z%^u^8x?CZB65L}>Pw8}be9kD0HVds8iXmZHK%H4pOTGvrh-B`RK{xLq6W}LDZH1Q* z06r?%5iNnO5BGq}Ok)9klc_)IRqz8T7PsTfS=+blJ$m$lTrX zv^28-N1;Z#gunWy_d!e%N^0#Zbog(3vFk7V3w>1dQo{gTXbq%l)q6j!jt!@j3qZ-Xl+cRx*NPY&vLQu-L0tm^B~}z2 zh+w{9-YR$=qUm*VM){&b;WY;M|~8E7!hmY za*qA}yccxDY#VDy2LAfV{lQ@$S zeweOlw(BhyvO$i+s>A{NG`S^KPR&9Omi3-9xMae%GvkJT$7O71Xs7w+L}HYU!$k%b z$BmV0nK*@_!y0S;Izj8(R#DTruNEo@uwzfkboT_+-^HeeHB1S zze?cDuV8>rgPM^FZu}yWCvn&~?9H?n+mo(rQ`Nz52$$6`N0$k+XjQv%DIHNCu^q zES0NvEA09{hQ0Uzncast;U=21Cz5pO_=knqlPz~Ssr%WB0naWrlc`0Iszsy94-3f9 zk}csvrPPTTA9?_`J#+^C0h#Xsa~G!|2A8D289oZf8)9wx;{yZCNO4xL6P)%7_Uvcf zL{A~+ZCWTQ1FS2|eFvzV0CIop6>ES3rxjt(SL7?luV3~Lba&frbur@nAJp$Nn62^B z7xMA)hBnI#<*?_Z=9qdROu4rC2Eag&fJW2${1t2jCjlEXMm+ha+B$Fn6kGm~D+I8C zCpE6Djei0pFSFD{dN>Bt+ypN5;$9*lgbjib`K6uQH$Q~Y(CCQW@X!J| zvde(fBQ^sMtOXjA5CxN_zMmy_IlVkfwo|(&X8#%rNWc>TGKcM_h7-y5==rH|DaDLu z5NiVY$quZfm4j=0V4My>8CqP$i%fhr9P9rDgGkHhO;7()Azz?YLE>5#7FPZ$8l$SP zlR`a`szV7ekR)A(c=RikM$xHwCpy~zp*;MfsRu+0D?#U(xT~1rJ`UFW&cQ_$-OzhT z0PB%TJ5q_^iXB}Hcyl33(9n}Wx;KkH$HArzU%Jgx8oEE_&K@e4sbB%AxJ$trQBV-n z+}!x_ej1(sQ|#ngk8^(^>Jvec$1X}?srNw&p}{`D^C-#`?^)o;H*1-eil1S+9xlz9 z<_U^@if;}*kw$W#ODiQrOQA2#Hd$j22tyOvT)UAajR8n>v`f%vC^?J2rmVKSK#GFH z!?}`7Fh;ge46RvnZfw5>}~_U45l$`}QDkO=d{ga))_*(U$7 zXhZmy_?=aEHF#^8W=1>zPgyT3wyCMX2U`<@xh~XxrL=sI$x}A=Di9dg{)$w2X=sI( zAE|vklOGjt$IWJWMSGrG!D&;Q=@4;Zn|kbK&hxu%W^MvD$!*v;2E;&2$ z8JQr${31pMmhQ7MVM;jV?Gd*(84meU$p+(-#KHxZ${)HFAQdNcGL=6%v9telew| z4XGMA)e=sOZ|G3?C~1vajNyZ0V!-z#?66?^PLoI&lN&q-_{5&9J&|L6@R7uL|1p;D8pc%Y2Q&m%Bh;X08mH2wxPiJZ=iZp|vZ zOau4~PJ0)_{`$fOB-^EIHJd$(ba}q==vc_xbtAR#8Q!aWN|JVLS7NG64srAlvCF>} zBa!IEoJyXtXpQh*L`C#>*iA&-g3cHYz&~QYWee81!dYi?bqPp z1<=>>!BT1P9q3cQAm@8e@R;#zKsIsvx@e137f)BoY8ky@8c|P*4#lp!iz9B*_H^nDbZjj|!V{@Az32dxHjpCdGn33F|~cM{U=Yhy(Y1@PkUj2-H2fjxYXz!bpjeReeuvVM8A4| z(xK?=S*!J@EZ#ct$QE;dqe%d#><>8X*6RHIhKT1wDD1;Hn=i%YE#bMS+Z6Z8kLg#B zFZNBQ0~q803_l?3w)~q6)qf#lY+_^gugkxG{QesTgwJ0vrtn(;*<6JQ`BXr4p9fl6 zi%rTP-8oUWLoA09^7%#1jPW4~NK5OSn;S;9RVSwajomrz!jNdso{oL`{XO7JPvAFt zO^@q%RVsg$LCOqL+x=kM1F=A=H<~k15Q={r{3DdiuH%AOdm@LaT#GXl zdeBbCyp4(m9@%YZTLM^ynO#2tOD%C_G%MU0S{rU$MMyAGyj%sAUH}_Ef^U;3uFq-Q zi#yMQ53xv>4qgys|3@Hy(!0CmVm>fmw-sgJqkdmR39?~Or6ZeDzJ4lZ96VFUQo(; z;@uaB;Tx|q?k@Tm9}JMxw}KTA5PxX=kmK^JZv}s!X}bT}#Qx*V>oNYn4)hrRmhgfr z`p*(EHLL_guOiGS^dK*?{fDxExxv&(|8OjSjF|~c+5#QOEP5`2d6fw)2`3zj07dMh zfp)9adXk&jY@{3RqT<@JQy2IR>Zaqi9$KD|==M(&|)TJaywas&f<8 zlxfE#t2r`xk9#EPdP(>nk{C3F1?zKL5JOhB@2Zu%u3j>5hHD^+zf~*pjzEe)F!wx3 z^86)?RJ(njsPN@b-@@7s!#P|QmGtg&*2^=w96D(aEGA*MJJ47-<#qj&LfO6J&jZLQ zCI+)pUa?K6$II6V)Mk*&ljnbtBW4QX7{$UgP^<23%86of03XAi3STSb$K>>1n~`8n z^DV^*ryYAbj^{;|$-MOwaS64C5&mHR#k|I7REc;zwbmo;cTCRV)J1;g5;nDt#5;dy z^}H0ZZc|>+41*n?JuCeDsChcIJ#>?Ob&GtH1^j_Y!tL5dqM`o-#WHK5l#rpF`7N8| z4Cj3f(;@I9>F#3Kycm_f6;+eyDm7Y}+CaalLHs$<#syqs$e-u-*FlWV0H+*&E;{zQ z+fz0PYQ@ZtUvE-8%lV>XnKVcuTt9J$ z$dM?qf1rtjn%Y`2kH7W?mIB<#WN$2%$+@O2k$%43a-5{J<#{U#p!FGAhBUt6mcEpf z(r8(vnV{oKb{R7k0>i!$E7>`-Y~sS<@vRrPt-aG*1J=my64)*6!7qJibCsWHhl z&_2`M4~0R2>e#44vfp61R4qlNy>Z=cb!hNP$^*(t{!??g%J%|zau6cgAgV7$*Gi}J zrrSZW9lpvVGXexHmzC?oc~G~A#p2= z;}>I-A%)t!jVa0k{@`np04$SqxVd({SW{j)#6G?_ZZXYxJsV|kJR7b8`?cW7l96QC zNio3$l`kN9V3?C^+H{*CLC=4p@E>GgQeOOmRS3@pK8f670lWr^UEpvvt&b7JT&(pD!`&6t(rY#+w>DKTuSpI!9$Tdgz3g0p_r z_QkEj>~UlcseKDf2Lb7Wz@H!!kop$L^}h+Ux3+V3HgWt9gMW|xt*}q$ucA2?f&#?m zEW{}M&wGC=7u)np!TxrGp_W_XF9PNX`Q+a25hD2fgtB5Nv8I{LW?h3SY}k`hkR#l@ z_Cyp*DoYy%hiI-_A37?XAGK-kXS%o>u36Se$I|WDx76)7dc99x(F{3f($0h!nUO+^ z9zmwP%V8Fr?$FwzOxKek))uQ*Ihcu&Nz1GJ+*>(7Yi73q_YpYc0X5(;O+}|cRs~IM ztYnAe1e9c95susgXfYF+(@t_u_a*j~LNnq1yOyBD>6)<4286Ymr5K$nXRbS21h2UG z^XP;T8m(5J<{~U$;(1Jh(fG!ukJr`j6)eBGi;jiSt-J2*)joGxrd-S#O$MG;aF)_U z9USY8H|v-a&lox=3Thw~!`qShK$1=|w8SIhuU(7+PU#zsIUy_(#oY-C$Iujg`HAZK z>^b*zEsMzl>|o1|U`HkQbj)?!Mn8!t-sG_`B~;XcAU=Cnejo$Sl9pIEdi5LEXl#Kk z6xr8>*7geszsnl)P4Ys}fi~DBPJq^jnO2_;)vsvJRGF7&_Nt9^*_y5dNP3?i2tm{K z&>XV#(2jB^DJ%C*r{ZZ&ssE`gF*zw+GOJ{K@KW_|1QKehc>}dqI+K&0_uH+D`CNQ% z%?B~+>v6>)-43M&{SoIV9{BXjU?#z<^-WJqq*BRtOd)NJ+kok7*^cU#)7^!mVOQ6Q zmvJsZZz$p&(BF2N=}qg~+$_jHuIf<(IPkyC@%=Y*E;jahjwWUnPT%PMec##mKSuv{ zhaC9V4ZE2QX&`!&f8KLex#p%g>qdo|jZaOiZnYB-1Vlmv5!WO!FAUBQqzO)t8!l3< zyha`9%B#j#Zh7$~Kqg~~>2Vk#i|6)>_CBz~^?4RL(3Z+2w`h)GhOUA%601WRbH1O| zNN?$CJX~HxNO+-PK4?hq%+Z!OerDn-`(e?tqVr78}TQe+}OUjuNEobZ*H8>uu)4($S7A2}oVU%0)TMytcpwg!2m&uVU@fDWyo#a3(>0kyiWc?5Q8a zm+yKyzaVM2dXs!OkDKDrxLxV?NBF|eYp$@|o1((a)TrC2ft`{a$s!+B9G`WE(0$_o zU9Cnd6UQjlyH|-Y+uxic7D7gb*FjO2#JJ)Xcifi*TUzhx##D>-EN{d}m6J{(E7*IE zV#+N<+0+H{j|oigE~SD%Lde7FDdYus7Ot0;*pc=Vu2)bMq9jCJd{=_2H&ykH&mJSg z#>*6j^UF6V4M|FyD=Gxf6a=<4K&lrJaa7hK4OGi?|6T-x?d zBKdvGyE^^I3@n^PlL5c!#_?>zQ}mQ4xHs=Zy`OO!%pl)r3N7S{Y9q~m_TRVxZuMLr zB0Qa(yrPsk!6ap>vxudE^!`j4xfep6VTo56>%mI@jP>t{xh^81%1a^p6&q14hOy=? zK8B_DV$xXLU${x~&@dG*MsgzulE;kJCOTZt8vc(O83qFK28I8%N0s-#)JPi_YiA2R zV-r^kqklJ`|L@q}R)=r@YB5HWfPvUtnTePmBA+LR=IG!~jf^Jmvy^D&@}>QqNc4<>o^=>5s(XEe+m7%zO@43^Qy3pZd(UMS=E zWCP?m7`3f47m?4J3NXs7uso)JAZ)Tvb z3n5rD=5fhSc>u*DN+u|_c}3l;nygCY;WN$2kvE`OB&p5WESez7NpY73-Bdwv{#Wr9 zmEIy580DAyF`Pzht=5I$CcH@_i+u5Yw0N zn~$`E-&yBNKa8$$?`93=LQ&Rs&KF9%s}K%`e6g>%|ByMt{cazJ9TlNO3JshhsggWI zt3rFBGI1A%E|2_aR$NgT17cRZzGMSV-7tm&0|s;hQGy;PQFeek>^CxQ9X={-E@!zt z=*H(@|GU&+eINLp0!vaZ9mX}&wpo(lOX;@tmo!<|HpQ*2Md}Cq8Acbq@g%zo|NS%- zshTtUqhxMrb)0PDBy_Pl3LlZud^R7zdj0Y7(b}u$)3qVmSsurfuu!_zo|g|5*2#mG zOsd!`w2CG!PWxwD7A<~ASL~}?ml1>v#Ln+TIX`nm++wi+#PDSwHzn=RsCGvdGe~Zg zD@*sqlM`@8-+)c1qe#CAfUVUYU6*fS=fEIg;Q06jpV8mMH2zCTbTV-kba7I(vvxN4 ze%NDY>-t?7E$nQSEo}ZR?teMtZ-55Te}}Zo2_Sng6BE;ey~%qol%`#jzWPSuRzA{o z%m*s&WJ{&F2~t4QRl0%CF{B6SR-gIfEis4#Ew}qEMK1vb@~>OrLch)~ z;ICi#0i*atEP0~YYZ66js@#6;Z|9Xm;u>abhDpbkmh6A7c^7z{D+arDalUjM?1jrA z@CiU^sP^8w2(MP^!%bN?q#OBpxRq(VD#ih`&kNh}Qnd7qcbXPa7u%f}Gw1oWACYRu z(r7@qJkZ%b+aubcAPKta+5(lsTt`)a@m?>k!~*WiAo62{%$@{; z8KgD*eMQQB3&>w?;}wF97DD9_B=gc8VDe`3n+B(0z4;po+q>0i8g`1nacrMVXa{=> z(K^)83%IBJs5VcNKJ4jQK!Q@>M=b`Z+UmXQ2j=~Xu5cBbAQ*jPS4{; zn6;R#mXaCi66_!iVCbGv$7&2UX#EJeZVY<$NAE$bDpNcHRa65^OQi7ElQUI%q))a5 ziy;w<<#CtsM13ub==cn;FKQB$kvqw&<0iESOG3_L2hn~!b1{`v!9LoZ*=9d_6(H}vpLS2Nf-}VHt+r~dRXed9z#*9~t;=|9+&6Ln`m+|y z1si=MD**+$fybBh9m4uXCidTv$r!i`xi~tS*g7kjyV(A_>-e_yKlc1h3a|NZQuGBt z^xxNgLJ#(;&%7uXJ@h)B;y98OSFHnpAi}YL;(lm-lH|ZZBoIRomo#PXlOCk+SJPCjZK+EO>L#b z!H@)!K49~ZKY%41c|BKcS~3(Fk(}iDtQbUKqS-aO9vDI=cen}MV61PNN78712*kP- z0%Thnv!;mh^-yTYn1koukc_?#&__s)$v{GDI26!lZtzkm+@bn8@gxTp`bf+byyt^l zrlRHeNSgrBlS!sXS`i!s`3PJP!-q!2654*r`Y<1}I4-bg_YKN4Ikz{POh!z zf%m6muTR9HoRE03davtGe`jbRz~mM{bQ^9@}J<%eBsILunF?TvS}PieX+bSr(`ywVM% zKa2llZd%M<$_7ILq0uzFXzCqao`eEXRA+}@K9i=7(s`fRzMDO3X0<2Ijnc2lH*u~VS%RMFcw&}>2(cq>n-JeQM;sduI&LsrX z*!Jg7yZeuSBsqXwV!t{HAc}JEWynuvl)<5~Pk|*m4%);%;Qrh(*tXNEi+$El!ukiL z$TWL=PlgL%kX&&5gZp>j@5!L^-zURAr@;TO=KlAC{+=Q-h+=;==su@_tp8XC_@w;o zO<}!*+SlA_(pd-THhkFt>o1cSMw4}rPoHJk7NSswCA%hq)-_>DoYe%4-X#%$Ed__o zE&(5(9LerLOb&+Rj}e5Sx6TB6yPA6HMcgvvynpD9PcQS3;z4UOfR z5}GPjb6XMp7YJ^l%C?DAS(up-IG-w^wV2zn8L zx&?2q?k6SfHb6y3safImmOjgm!&+YzkA1Bar|0uz`$JPs*-aeLfv?c@Sq`z%^)D}D zQEUoK2WGQr8>^tp-Ki?y(*6L0G=bym4z4_ZOXK+O()0|z1N}SM|J7lC*V}&${jI3+ z@$Z!hR&Ri;p#R8jeU)opl%_3OK&uZaR)3ielcoNAq%mOEsRe%nwjVapMfR*Y^U-u& zmvm4zmm!I>bYb~ekjT^aH_1EkiSBZc;>mGadhQE0bsMHMCQ~^Vrc*_vFnH*ef(3k@ z6x`vK7N>QrvfDE?X0WIUAGF*>!s7XYzVV_ITSgSkJD%C#y+4iinJv}MFZ zmq$6#`4L48T*8jrZJx+$#|u-F^0S)Oz`U> z=4Q2`4`dAY@gnP5cLbG%tdn;u@!vss4;Bl4p_qIALV;XRgn0HEgyi#8)@vT}ZPs zTv0uS+9eThn;`rY2qiTqmW$cXbb!j24Xw z*#_P5>NGdU219RrzLawcKs-uY^7`5|60_z&Sbsd>x}Vq4b~A7~CGn+VcL(%#>}gf0 zlZOFbI6inOmlj~l1-IoJ$Y^sG6E5+A;o=9s;))7>a>BIE8S|6!tAth?K-P^1Uhdmf zGcd>k7(R!NZOpeTrvKu~*~H0NPl8eCU#R}I{kN$8!@r_?lC=K2E`2`=`+nl_x+j}f z5?P5a)mPDlgPQy!0);YU_pDq55*=2R4F$WbqG+33tK%>XPv+)%7I<=Na-=#}sN>!Z zm!q@M_v;JSVL019s8UX=abB#(wd0x_npLFlz6Fa@h2yg)*B4E1+DC2d*qIRBL<{2X z!EgL>uW7>3EFw_|ll4p;AIDAzX@yia9vpHd((3Y8l`WK;BpSK>tW_`;q3~RK3OQt# z3Wz43bzGDd9gStTAl`T!I$LymF^y`^`bU?A928cMzJB6L?c}AY;v#pzT}nP=AebXx z_56g?9kiXBz`4o$j7}S}3fm}Ka%i#nJFb!vSnk*jLKm=FF{2712IZDoxq%WsyQOl2 zaB=Y*O@k({qF~;^aZf&OIQ67afMVd2LG$PZbRH{mUG`F#j`Gzx>?g)2zk`xYHF)%B z*lF|W5cBjPmAYWz3-vQ|Qq>MMXRS!|tz=cm3Rk|&aK0{N^ii7teE22pErz1?_LY8h znidb=`<%Z`9A_*ik>2qe#$J+*hM8T-=lrTA3-RE?FYCqEJt*Zr_I+Yo*!r)gCmyux zW)BXVvmrA-qW-ZEJun>rBxnZ!KZ+)}`)waqdT}im z@lNfs(xAF{yFI#11{=_sK7f2-b!DVjw+&}v?#{VPCX$&T3but2<~8Ha)kp1><<(2l zhvEb?0KyNRp0v*(P!gKar7t^I%Xm*4x11{&gG$2-Sky6C>kg>XAMcT+BO~V9(;#Q* zq}phnAumj47pR(y-8zb?+1&2W&SbojNvGu7XnKmKadPpy*{-Emq{4d%Ph;liM8kA^ zBh!oz=lMrp__ostmnTsjD@+@>g;Xn^rq7QaMHx}E{5R)?#?Q;(N4_*s_Y+ULd|AMy zN&}$<&6VuV2FUH2@upWAFbFJz z=aK}fLEb!?96x90i@1#KLK5p3J50P+i`nOD0A|vDhk4L5BLy$Xe!7y@@1whX(0Dat zl1~_zSxI1B%V4#AKIBW}oCyuUO24-_q#r(r)n|s~8SV_?prA%}o1)s}ySf8gKvokc zq*}P4fuk?zou(v1XPaVtn^F2ef_hBnc@;G54lYMt2$6Zau5tna@@a-iLEdd zfQJyTE+sPC#e{|{;E1_!u~Cr~6cY31H#tSU{E`=EJ2Y*ooRuGN^boLj1K0X7cm~cm zNVW=R1k~UL9M~#2Ku$n_Y}Yb}CKH!|JOebClTVOr8gG!6mdS?gt^&}8Z8DbORm<{T zBjg}~lDndVPj6XpQd$2DgTe=eZz80e3vr1$A#&Yu2xz^eFqm`nZ2YEA zSAjhglTh>Qzyt{@qJ8J0fwc@NT2NCX(@_$Gu)g6crmB-nQmZ(qtiq>1Ag8D}ZK3H; z3p=`1oOYXu=Pl4oX~=U*IG$kiJf$-Lkg|Nl0B7_FphA#82KG-$tUUz08kUrJJ?j8n z)SV9tz^IFFq>k}sH<}(2B~=y_a%mr0m2-0GuS*Z%#KeOssoS%t*>bHsclYj^PDB&% z3RcIU)Wy656!yU7;z&o=xUi6H z5mG=<5FcIzOUzxc!!~PxYKLufK?l$C3#Oev1)rnBH%>X9Y{^7g5|*Z9W=;lv_Ps{9JtL4!4R@Q!)71W4qaDH*wUHZVlFHP^u6qC8MeBAS)l`)rwdGB3Bgy{lt>SveV zKq*pChPrrBRPx`K1pW0aosBeFY{YhClTjd`{G75qLh(2LRj6sr&JuhTx2}Wn>S3O=TCa z#13UzB^@Gr{C<2YZg@2<%P(G(kk_U85}%&p&JV*sS0aN(r55ir6zj`wwWC0u1-A! zC4{oH;9|W214QH_&HeK6NdS6BR`_XoV<92;>DkXW(hez~AAcMKe+4UuiGNn7JChmu z`1+4lNVy*&$a z%)hdL!tK=2q<%`28r_$WBC?Pmy;~Ai$7^fJhA{9VjIgcro9xBOG)m4opn0V^?2{0%G;XLp-llLXqnRpro z1=8{DB(#k_ZSqHxY5HWemS#Orn`rQ8JV3(5?XN@bt?)v`E)DAfcAZHODDewn)n7E! z^D*tFd4GPwIlkBuP-AH@FqBI0dnX4+^j;UUb2ct8gJ7LR=2YO$Zi(XO#ySS+i8ts# zk>ug5mUtoKZ(89Cl9C})RFN{w;UVT6N7H6cIlgIIhQ#YLCi|=!@dKVl)2OC~JWd@* z5eW;%tnn&3mc|j&){p3F3&==FAg_#Jtw<1ZCl(gZ5I>k$8@t-e%29vF3xg=|-1=S) zgmieS;mT%g#)y2RhZsgNaZ#3zcW$|4lM7hL+I zn43bVmWY`jrOK?=*6U9u{6_xgP{Iu^8a2|0O}Lc*5MGb`tw9U<@+Kf%Sf86$Vi7Ka z!zT&WuUPE|LCZ}F=EPTZRTYu=)=rnwTEd^^j8in=^pM8x%Ef2Vziagsr?fs6TUFDa zChtZPcS=Y)+$JRb&JH`WlXaaC<#!G%Xfj6WN%&Qo#nEHJUleG3MHdl{$%+&e*R@CB za&4&T#?X^;nHD8FVXy;x9s%Whweab}%A?CFBnN&@1J83<-t{i z?^5ddt}Ye6`}CEUE3oa7^_#(AN_wJJddkgtffYD>%8%}Rm9ainx+P1;Lm7|=o!);E zz7_tEn7|?*8x204EM$fjYF_MY-kZFz&7ab?z&RPB!MCOGTs_NTxirceaj;N&Ef`Nr zYT}{mW=8UbS*WgN{xYKXNFrwU<%84iPPJ73I-#ws{n9*J#XnG#8aF$3j9od$jjI%3 znKZcdGWWcdN)DNry^l1YgIU8HVUr|-J%5cx(ZkC&(8JC=^z#%4_q=id$@=~vX1#sw zg_>atK58%e5iXB@_!e`VHmO2*ORb93;!Xgv~Uzz!}lI%B%pjLic+1>#1t%esjVc1FSn!?wkI(;hm z%Z-Tx7iKJ8hoxpI|L>9(P~Fs50yZi32Go56=_}67o?_+JxzdsR9mkfHN-@0C`^D<~ zY z1EPezp%4SND^JuEDDLQq1dwgHjH13IJ}xas!HC5+xeDOn`8a$MvwuS3jW743BqmX%C zQ(sjc)^=h@mvUSut-+;wKHLmY=V;jQFj|{d%x1xA%6+8tQ%Q`m)h}%@f2vVbF41*Q zc7@c%6^GGLeRmBV@E|Sp_>Dj@fZsg>{D1Zg=HDGd&cI)$XFn!*!c?#jgc;!~FIQ-oi5Q~Z( zN}ch=@8716)|oM`p}T~EJ8^~+B4pfPbMU+LLAXC~b1%RCl10xuu8+ z-mll&{732Oq?B`;1O6N`1tT zDEYxl+4Y+|rz73?H=Dg8i8xq3M#yTHC815?O|~5jp7^upqz!f_0!7L6pe}_MJ3+o4 zJq?}`$Va&yPek^&^~fR5(?A1k^)5i<6jO3PQuwH8B4~<54vAPY*7KEz9LR`;n6eo2 zxQPY^H1iZ2bW3A91eXj1`$d8fvUdyosI?oOH|Q3Eagfwd`P}ifXg#m{B4l~P8woD*=ZPq7!tRjg?q81}R>7im1*8!jK5HP6HX^Gg zj>Qpyh$*NGbL=kTfa$SMG@1QgOAu&#j=nMp-Qfe|obN&lmuRJM`N~p*T6#3sFgX6^ zl1`qvvae0@l+ARCHG&p7gN@y_P)unlV4wUf?tU4mVq~T$-{(X|Sm&9XFB8ppv8$M2 zSRi1n8pM2az0}t1nB@3}l^JN;$()*TpGK3g%5u`?;dJolq$U*Li|iCKYJuACW+BID zOiC)hcnh#(AYBFihr?Chns8qWQj+?LkdX-h__?=qKxkzZNL!Oq)^$I!y!)y@a(o9s z`9JfX0_3G;R!&kQ07>f~P0Z-64IxzPL%V`5CJ>Zt+b(X;GbJaYtRtQTBbME*lUigj zb}@TkF~I?q39dv@?M}GD~l8;t;+CO%?o(#dzuh^$QY{96pegZ97{=~b*GeQxC|sNfC1 z)CiC-%Lc`QyL{IkD@a>neqQ2moS8Il~{Su_VY~Uo#m__J4#~|*xVjXvUBvW)V5x0>{@}=H6 zTI4nrIyaQM=0tMQO9p$K77G<>R=^5;E*eCaFP)2ogX8^At^h+T{WjEz#+o1EQQmP3 zh-P;rrob!K-f%`P9Q>%`9x|~4UlVRlw=n%&ZQ-pC+gt5OzWiE)nVnSEdbobDPs573 zgjjhDCiXu=kQ9h7>_`kAF1wU!t{Q*j4slpm5tM3Et`Wv=!}Y-WuzM<*-!{}e`Db4s z_(j!6IU)EQhb@~7o*1xR3e0W5S}!I!PZ#rw@>AHK==8fFX2UjSF=iRg_>{eepK)BF z)VwEPXG4N}*L{uA;M=hccx68WS@ESA=Sy7oiv3B+buKCz{KI!2$M<0O5$(n2%F}M+5N!;^KYRstf0j7kz)uZTT zFmv3rJb}EZYYR3t2^6V#yIw8}`sJ)U!S88d%0DVhR*WR7u}1QU;7>O|n##M#*5v5X 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 gfWL7tC56lLVNy +#include +#include //V.1.11.9 +#include //V.2.5.7 +#include // ROB TILLAART V.0.4.4 +#include + +// ======================== CONFIGURACIÓN BÁSICA// ===================================================== +// PANTALLA OLED *No altere la resolución +#define OLED_ADDR 0x3C +#define OLED_W 128 +#define OLED_H 64 +Adafruit_SSD1306 display(OLED_W, OLED_H, &Wire); + +// INA226 *Cálculo de medición en referencia al valor de la fuente +#define INA_ADDR 0x40 +INA226 ina(INA_ADDR); +// Calibración campo +const float SHUNT_OHMS = 0.005f; // 5 mΩ, si tienes otra resistencia cámbiala en tu placa a esta +// Si metiste divisor de tensión de 100k/100k a VBUS multiplicamos x2: +const float V_CAL = 1.0501f * 2.0f; // ajusta fino de voltaje +// Ajuste el fino de amperajes +const float I_CAL = 0.810f; // Este ajuste es para (2.47A → 2.00A) relación 1:! + +// CONFIGURACIÓN DE PINES +#define RELAY1 7 // canal 1 con SAFE / Led indicador +#define RELAY2 6 // canal 2 libre / Led indicador +#define BTN1 9 // Botón 1 +#define BTN2 10 // Botón 2 +#define BEEPER 5 // Beeper + +// =============================FIN DE CONFIGURACIÓN// ================================================ + +enum Ch1State { + CH1_IDLE = 0, + CH1_ACTIVE, + CH1_TRIPPED +}; +Ch1State ch1State = CH1_IDLE; + +bool wakeSwallowInputs = false; +bool safeModeEnabled = true; +float tc_lastAdjV = NAN; +float tc_lastAdjA = NAN; +const float SAFE_SHORT_VOLTAGE = 0.40f; +const unsigned long SAFE_ARM_DELAY_MS = 200; + +enum SafeMode : uint8_t { SAFE_V = 0, SAFE_OFF = 1, SAFE_PLUS = 2 }; +SafeMode safeMode = SAFE_V; + +bool lastTripWasPlus = false; +unsigned long ch1OnMs = 0; + +float tc_aPrev = NAN; +float tc_aP2P = 0.0f; +float tc_aStepMax = 0.0f; +uint16_t tc_runMs = 4000; +uint16_t tc_settleMs = 200; +bool lastShortTrip = false; +bool tripScreenDrawn = false; + +enum TestChipSel_TC : uint8_t { + TCSEL_CPU = 0, + TCSEL_GPU = 1, + TCSEL_VRAM = 2, + TCSEL_BUCK = 3, + TCSEL_COUNT = 4 +}; +uint8_t testChipSel_TC = TCSEL_CPU; + +enum TestChipPhase_TC : uint8_t { + TCPH_READY = 0, + TCPH_ARMED, + TCPH_MEASURE, + TCPH_RESULT +}; +TestChipPhase_TC testPhase_TC = TCPH_READY; + +enum LaptopPhase { LAPH_ADJ = 0, LAPH_ANALYSIS = 1 }; +LaptopPhase laptopPhase = LAPH_ADJ; + +enum TestChipResult_TC : uint8_t { + TCR_NONE = 0, + TCR_OK, + TCR_VLOW, + TCR_VHIGH, + TCR_SHORT, + TCR_FAIL, + TCR_NOCON +}; +TestChipResult_TC lastResult_TC = TCR_NONE; + +bool RELAY_ACTIVE_LOW = false; +inline void setRelay(uint8_t pin, bool on){ + digitalWrite(pin, + RELAY_ACTIVE_LOW + ? (on ? LOW : HIGH) + : (on ? HIGH : LOW) + ); +} + +bool relay1=false; +bool relay2=false; + +const char *FW_VERSION = "Tester v1.0"; + +const uint16_t TC_TRACE_MAX = 120; +float tc_trace[TC_TRACE_MAX]; +uint16_t tc_traceCount = 0; + +uint8_t tc_finalCode = 4; + +float tc_vMin = 99.0f; +float tc_vMax = 0.0f; +float tc_vSum = 0.0f; +uint16_t tc_cnt = 0; +float tc_lastCheckVolt = 0.0f; + +float tc_aMin = 99.0f; +float tc_aMax = 0.0f; +float tc_aSum = 0.0f; + +float tc_vMinStable = 99.0f; +float tc_vMaxStable = 0.0f; +float tc_aSumStable = 0.0f; +uint16_t tc_cntStable = 0; +float tc_aMinStable = 99.0f; +float tc_aMaxStable = 0.0f; + +unsigned long tc_stableStartMs = 0; +float tc_aSumEarly = 0.0f; uint16_t tc_aCntEarly = 0; +float tc_aSumLate = 0.0f; uint16_t tc_aCntLate = 0; + +unsigned long tcMeasureStartMs = 0; +unsigned long TC_MEASURE_WINDOW_MS = 2000; + +const float TC_OK_MIN_V = 0.90f; +const float TC_OK_MAX_V = 1.10f; +const float TC_SHORT_V = 0.30f; +const float TC_HIGH_V = 1.40f; + +bool laptopNeedsAdj = true; + +const float LAPTOP_VMIN = 18.0f; +const float LAPTOP_VMAX = 21.0f; + +const float L_NO_POWER_A = 0.010f; +const float L_STBY_MIN_A = 0.015f; +const float L_STBY_MAX_A = 0.050f; +const float L_COMM_SWING_A = 0.020f; +const float L_BOOT_A = 0.50f; +const float L_SHORT_I = 3.0f; +const float L_SHORT_DROP_P = 0.10f; + +static float lap_aFast = 0.0f, lap_aSlow = 0.0f, lap_aSwingPeak = 0.0f; +static float lap_vBase = 0.0f; +static bool lap_lastRelayOn = false; +static bool lap_wasAboveStandby = false; +static unsigned long lap_lastAboveTs = 0; +static unsigned long lap_lastSampleMs = 0; + +#include +void delaySafe(unsigned long ms){ + unsigned long t0 = millis(); + while (millis() - t0 < ms){ + wdt_reset(); + delay(10); + } +} +void repromod_dualButtonResetHandler(){ + static uint8_t state = 0; + static unsigned long t0 = 0; + + const bool b1 = (digitalRead(BTN1) == LOW); + const bool b2 = (digitalRead(BTN2) == LOW); + + switch(state){ + case 0: + if (b1 && b2){ + t0 = millis(); + state = 1; + } + break; + + case 1: + if (!(b1 && b2)){ + state = 0; + break; + } + if (millis() - t0 >= 250){ + + for (int i = 0; i < 3; i++){ + setRelay(RELAY1, true); + setRelay(RELAY2, true); + delay(120); + + setRelay(RELAY1, false); + setRelay(RELAY2, false); + delay(200); + } + + setRelay(RELAY1, false); + setRelay(RELAY2, false); + + wdt_enable(WDTO_15MS); + while (true) { } + } + break; + } +} +void beepOnce(uint16_t ms){ + tone(BEEPER, 3000); + delay(ms); + noTone(BEEPER); +} +void beepCount(uint8_t n, uint16_t onMs=80, uint16_t offMs=80){ + for(uint8_t i=0;i DEBOUNCE_MS){ + *debPtr = now; + if(reading == LOW){ + clicked = true; + } + *lastPtr = reading; + } + return clicked; +} + +bool checkLongPressB2(){ + bool b2Now = (digitalRead(BTN2)==LOW); + unsigned long now = millis(); + + if(!b2Held){ + if(b2Now){ + b2Held=true; + b2EscapeDone=false; + b2PressStart=now; + } + } else { + if(!b2Now){ + b2Held=false; + b2EscapeDone=false; + } else { + if(!b2EscapeDone && (now - b2PressStart)>=B2_LONG_MS){ + b2EscapeDone=true; + return true; + } + } + } + return false; +} + +bool voltCheckExitHold(){ + static bool vHeld=false; + static unsigned long vStart=0; + static bool fired=false; + + bool nowPressed = (digitalRead(BTN2)==LOW); + unsigned long nowMs = millis(); + + if(!vHeld){ + if(nowPressed){ + vHeld=true; + vStart=nowMs; + fired=false; + } + } else { + if(!nowPressed){ + vHeld=false; + } else { + if(!fired && (nowMs - vStart)>=B2_LONG_MS){ + fired=true; + return true; + } + } + } + return false; +} + +#ifndef REPROMOD_COMPAT_FWD_INCLUDED +#define REPROMOD_COMPAT_FWD_INCLUDED + +#include + +enum TestChipResult_TC : uint8_t; + +void beepOnce(uint16_t ms); +void beepCount(uint8_t n, uint16_t onMs = 80, uint16_t offMs = 80); + +extern const unsigned long SAFE_ARM_DELAY_MS; + +class Adafruit_SSD1306; +extern Adafruit_SSD1306 display; + +void setRelay(uint8_t pin, bool on); +void readVA(float &voltsOut, float &sOut); +void registerActivity(void); + +extern bool relay1; +extern unsigned long ch1OnMs; +extern bool lastShortTrip; + +#endif + +bool displaySleeping=false; +unsigned long lastActivityMs=0; +const unsigned long OLED_SLEEP_MS = 20000; + +void registerActivity(){ + lastActivityMs = millis(); +} + +void oledOff(){ + if(!displaySleeping){ + display.ssd1306_command(SSD1306_DISPLAYOFF); + displaySleeping=true; + } +} +void oledOn(){ + if(displaySleeping){ + display.ssd1306_command(SSD1306_DISPLAYON); + displaySleeping=false; + } +} + +bool rawBtnPressed(){ + return (digitalRead(BTN1)==LOW) || (digitalRead(BTN2)==LOW); +} + +enum UiMode { + MODE_HOME = 0, + MODE_MENU, + MODE_TEST_CHIP, + MODE_VOLT, + MODE_LAPTOP +}; +UiMode uiMode = MODE_HOME; + +enum MainMenuItem { + MM_CANAL2 = 0, + MM_TESTCHIP, + MM_MEDICION, + MM_LAPTOP, + MM_COUNT +}; +uint8_t mainSel = 0; + +enum TestChipItem { + TC_CPU = 0, + TC_GPU, + TC_VRAM, + TC_BUCK, + TC_COUNT +}; +uint8_t testChipSel = 0; + +const uint8_t PW=118; +const uint8_t PH=24; +const uint8_t GX=5; +const uint8_t GY=20; +uint8_t gbuf[PW]; +uint8_t head=0; + +float aHist[4] = {0,0,0,0}; +unsigned long tHist[4] = {0,0,0,0}; +uint8_t aHidx = 0; +bool aHistPrimed = false; +const float GRAPH_MAX_DROP = 1.0f; + +float vref = 0.0f; +const float VREF_HYST = 0.40f; +const unsigned long VREF_REARM_HOLD_MS=1000; + +#define INA_SHUNT_REG 0x01 + +float readVoltageCal(){ + return ina.getBusVoltage() * V_CAL; +} + +int16_t inaReadRegister16(uint8_t reg){ + Wire.beginTransmission(INA_ADDR); + Wire.write(reg); + Wire.endTransmission(false); + Wire.requestFrom(INA_ADDR,(uint8_t)2); + if(Wire.available()<2) return 0; + uint16_t msb=Wire.read(); + uint16_t lsb=Wire.read(); + uint16_t raw=(msb<<8)|lsb; + return (int16_t)raw; +} + +float readShuntVoltage_mV(){ + int16_t raw=inaReadRegister16(INA_SHUNT_REG); + return (float)raw * 0.0025f; +} + +void readVA(float &voltsOut, float &sOut){ + float vOut = readVoltageCal(); + float shunt_mV = readShuntVoltage_mV(); + float shunt_V = shunt_mV / 1000.0f; + float amps = (shunt_V / SHUNT_OHMS) * I_CAL; + + voltsOut = vOut; + ampsOut = amps; +} + +float aSlow = 0.0f; +float aPeakSwing = 0.0f; +unsigned long lastVoltSampleMs=0; + +const float FLUCT_THRESH = 0.01f; +const float PULSE_THRESH = 0.05f; + +void drawLaptopAnalysisSimple(const char* msg, float ampsNow, bool relayOn){ + display.clearDisplay(); + display.setTextColor(SSD1306_WHITE); + + display.setTextSize(2); + display.setCursor(18, 0); + display.print("ANALISIS"); + + display.fillRect(0, 16, 160, 14, SSD1306_WHITE); + display.setTextColor(SSD1306_BLACK); + display.setTextSize(1); + display.setCursor(2, 20); + display.print(msg); + display.setTextColor(SSD1306_WHITE); + + display.setTextSize(2); + display.setCursor(10, 34); + display.print(fabs(ampsNow), 3); + display.print("A"); + +float vNow = 0.0f, aNow = 0.0f; +readVA(vNow, aNow); + +display.setTextSize(1); +display.setTextColor(SSD1306_WHITE); +display.setCursor(90, 40); +int vDisplay = (int)vNow; +int vDec = (int)((vNow - vDisplay) * 10); + +if (vDisplay < 10) display.print('0'); +display.print(vDisplay); +display.print('.'); +display.print(vDec); +display.print('V'); + display.fillRect(2, 52, 40, 14, SSD1306_WHITE); + display.setTextColor(SSD1306_BLACK); + display.setTextSize(1); + display.setCursor(5, 54); + display.print("B1 "); + display.print(relayOn ? "ON" : "OFF"); + display.setTextColor(SSD1306_WHITE); + + display.setCursor(94, 54); + display.print("B2<"); + + display.display(); +} + +void drawInvertedLabel(int16_t x, int16_t y, const char *txt){ + int16_t bx,by; uint16_t bw,bh; + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + display.getTextBounds(txt, x, y, &bx,&by,&bw,&bh); + + display.fillRect(bx-2, by-1, bw+4, bh+2, SSD1306_WHITE); + + display.setTextColor(SSD1306_BLACK); + display.setCursor(x,y); + display.print(txt); + + display.setTextColor(SSD1306_WHITE); +} + +void drawHomeIdle(){ + display.clearDisplay(); + +display.setTextSize(1); +display.setTextColor(SSD1306_WHITE); + +display.setCursor(3, 54); +display.print("B1 canal1"); + +display.setCursor(79, 54); +display.print("B2 menu"); + +display.setTextColor(SSD1306_WHITE); + + { + const char *txt="REPROMOD"; + int16_t x1,y1; uint16_t w1,h1; + display.setTextSize(2); + display.getTextBounds(txt,0,0,&x1,&y1,&w1,&h1); + + int16_t boxX = (OLED_W - (w1+6)) / 2; + if(boxX<0) boxX=0; + int16_t boxY = 8; + uint16_t boxW = w1+6; + uint16_t boxH = h1+4; + + display.fillRect(boxX,boxY,boxW,boxH,SSD1306_WHITE); + + display.setTextColor(SSD1306_BLACK); + display.setCursor(boxX+3, boxY+2); + display.print(txt); + + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + int16_t vx,vy; uint16_t vw,vh; + display.getTextBounds(FW_VERSION,0,0,&vx,&vy,&vw,&vh); + display.setCursor((OLED_W - vw)/2, boxY+boxH+6); + display.print(FW_VERSION); + } + + display.display(); +} + +void drawTripScreen() { + display.clearDisplay(); + + display.setTextSize(2); + display.setTextColor(SSD1306_WHITE); + display.setCursor(2, 2); + display.println(F("PROTECCION")); + + display.fillRect(0, 28, 128, 14, SSD1306_WHITE); + display.setTextColor(SSD1306_BLACK); + display.setTextSize(1); + display.setCursor(4, 32); + if (lastTripWasPlus) { + display.println(F(" Pico de corriente")); + } else { + display.println(F(" Caida de voltaje")); + } + + display.setTextColor(SSD1306_WHITE); + display.setTextSize(1); + display.setCursor(2, 54); + display.println(F("B1> Rearmar")); + display.setCursor(98, 54); + display.println(F("B2<")); + + display.display(); +} + +void drawChannel1Frame(float v, float a, float dropV, float pct, bool safeOn){ + display.clearDisplay(); + + display.fillRect(0,0,128,12,SSD1306_BLACK); + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + display.setCursor(0,0); + display.print(v,2); display.print("v "); + display.print(a,2); display.print("A"); + + const int16_t STATUS_X = 76; + const int16_t STATUS_Y = 0; + const int16_t STATUS_W = 52; + const int16_t STATUS_H = 12; + + display.fillRect(STATUS_X, STATUS_Y, STATUS_W, STATUS_H, SSD1306_BLACK); + + switch (safeMode) { + case SAFE_V: { + display.setTextColor(SSD1306_WHITE); + display.setCursor(STATUS_X + 12, STATUS_Y); + display.print("SAFE"); + } break; + + case SAFE_PLUS: { + display.setTextColor(SSD1306_WHITE); + display.setCursor(STATUS_X + 6, STATUS_Y); + display.print("SAFE+"); + } break; + + case SAFE_OFF: { + + static bool blink=false; + static unsigned long t0=0; + unsigned long now=millis(); + if (now - t0 > 500) { t0 = now; blink = !blink; } + + if (blink) { + display.fillRect(STATUS_X, STATUS_Y, STATUS_W, STATUS_H, SSD1306_WHITE); + display.drawRect(STATUS_X, STATUS_Y, STATUS_W, STATUS_H, SSD1306_BLACK); + display.setTextColor(SSD1306_BLACK); + display.setCursor(STATUS_X + 6, STATUS_Y + 1); + display.print("NO SAFE"); + display.setTextColor(SSD1306_WHITE); + } else { + display.setTextColor(SSD1306_WHITE); + display.setCursor(STATUS_X + 6, STATUS_Y); + display.print("NO SAFE"); + } + } break; + } + + const uint8_t PLOT_TOP = 14; + const uint8_t PLOT_BOTTOM = OLED_H - 18; + const uint8_t plotH = (PLOT_BOTTOM > PLOT_TOP) ? (PLOT_BOTTOM - PLOT_TOP) : 1; + + for(uint8_t band=1; band<=4; band++){ + uint8_t yGuide = PLOT_TOP + (uint16_t)plotH * band / 5; + for(uint8_t x=GX; x < (uint8_t)(GX+PW); x++){ + if((x % 4) < 2){ + if(yGuide < OLED_H) display.drawPixel(x, yGuide, SSD1306_WHITE); + } + } + } + + { + uint8_t x = GX; + uint8_t idx = head; + for(uint8_t i=0; i1)?(PH-1):1)); + uint8_t baseY = PLOT_TOP + yScaled; + + for(int8_t dy=-1; dy<=1; dy++){ + int16_t yy = (int16_t)baseY + dy; + if(yy>=0 && yy999) mvInt=999; + int pctInt=(int)(pct+0.5f); + if(pctInt<0) pctInt=0; if(pctInt>99) pctInt=99; + + display.setTextSize(2); + display.setTextColor(SSD1306_WHITE); + display.setCursor(2, OLED_H-18); + + if(mvInt<100) display.print('0'); + if(mvInt<10) display.print('0'); + display.print(mvInt); + display.print("mV "); + + if(pctInt<10) display.print('0'); + display.print(pctInt); + display.print("%"); + + display.display(); +} + +void drawTestChipMenu_TC(uint8_t sel, TestChipResult_TC pendingRes){ + + display.clearDisplay(); + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + + display.setCursor(30,0); + display.print("TESTER CHIP"); + + if(sel==TCSEL_CPU){ + drawInvertedLabel(10,14,"CPU"); + } else { + display.setCursor(10,14); display.print("CPU"); + } + + if(sel==TCSEL_GPU){ + drawInvertedLabel(90,14,"GPU"); + } else { + display.setCursor(90,14); display.print("GPU"); + } + + if(sel==TCSEL_VRAM){ + drawInvertedLabel(10,30,"VRAM"); + } else { + display.setCursor(10,30); display.print("VRAM"); + } + + if(sel==TCSEL_BUCK){ + drawInvertedLabel(90,30,"BUCK"); + } else { + display.setCursor(90,30); display.print("BUCK"); + } + + display.setCursor(4,42); + display.print("Ajuste 1v a 4A B1>"); + drawInvertedLabel(6, 54, " DESCONECTE PINZAS!"); + +} +void drawTestArmed_TC(uint8_t sel){ + + display.clearDisplay(); + display.setTextSize(2); + display.setTextColor(SSD1306_WHITE); + + switch(sel){ + case TCSEL_CPU: display.setCursor(0,0); display.print("CPU"); break; + case TCSEL_GPU: display.setCursor(0,0); display.print("GPU"); break; + case TCSEL_VRAM: display.setCursor(0,0); display.print("VRAM"); break; + case TCSEL_BUCK: display.setCursor(0,0); display.print("BUCK"); break; + } + + display.setTextSize(1); + display.setCursor(0,24); + display.print("Pinza + en placa"); + + display.setCursor(0,34); + display.print("Pinza - en GND"); + + drawInvertedLabel(0,50,"B1 TEST"); + display.setCursor(60,50); + display.print("B2<"); + + display.display(); +} + +void drawTestMeasuring_TC(uint8_t sel){ + + display.clearDisplay(); + display.setTextSize(2); + display.setTextColor(SSD1306_WHITE); + + switch(sel){ + case TCSEL_CPU: display.setCursor(0,0); display.print("CPU"); break; + case TCSEL_GPU: display.setCursor(0,0); display.print("GPU"); break; + case TCSEL_VRAM: display.setCursor(0,0); display.print("VRAM"); break; + case TCSEL_BUCK: display.setCursor(0,0); display.print("BUCK"); break; + } + + display.setTextSize(1); + display.setCursor(0,28); + display.print("Comprobando V..."); + + display.setCursor(0,40); + display.print("Mantenga 1v estable"); + + unsigned long elapsed = millis() - tcMeasureStartMs; + uint8_t barW = (elapsed >= TC_MEASURE_WINDOW_MS) + ? 100 + : (uint8_t)( (elapsed*100UL)/TC_MEASURE_WINDOW_MS ); + display.drawRect(0,56,104,6,SSD1306_WHITE); + display.fillRect(1,57,barW,4,SSD1306_WHITE); + + display.display(); +} + +void drawTestResult_TC(uint8_t sel, + TestChipResult_TC res, + float vAvg,float vMin,float vMax) +{ + display.clearDisplay(); + display.setTextColor(SSD1306_WHITE); + + display.setTextSize(2); + switch(sel){ + case TCSEL_CPU: display.setCursor(0,0); display.print("CPU"); break; + case TCSEL_GPU: display.setCursor(0,0); display.print("GPU"); break; + case TCSEL_VRAM: display.setCursor(0,0); display.print("VRAM"); break; + case TCSEL_BUCK: display.setCursor(0,0); display.print("BUCK"); break; + } + + display.setTextSize(1); + + const char *msgMain = ""; + bool invertMsg=false; + + switch(res){ + case TCR_OK: msgMain="CHIP OK!"; invertMsg=false; break; + case TCR_VLOW: msgMain="V BAJO"; invertMsg=true; break; + case TCR_VHIGH: msgMain="V ALTO"; invertMsg=true; break; + case TCR_SHORT: msgMain="CORTO!"; invertMsg=true; break; + case TCR_NOCON: msgMain="SIN CARGA";invertMsg=true; break; + case TCR_FAIL: + default: msgMain="CHIP FALLA"; invertMsg=true; break; +} + + if(invertMsg){ + drawInvertedLabel(0,24,msgMain); + }else{ + display.setCursor(0,24); + display.print(msgMain); + } + +float useMin = (tc_cntStable>0) ? tc_vMinStable : vMin; +float useMax = (tc_cntStable>0) ? tc_vMaxStable : vMax; + +float dropPct = 0.0f; +if(useMax > 0.001f){ + dropPct = (useMax - useMin)/useMax * 100.0f; + if(dropPct < 0) dropPct = 0; + if(dropPct > 99.0f) dropPct = 99.0f; +} + + display.setCursor(0,36); + display.print("Media "); + display.print(vAvg,2); + display.print("v"); + + display.setCursor(0,46); + display.print("MIN "); + display.print(vMin,2); + display.print("v"); + + display.setCursor(64,46); + display.print("MAX "); + display.print(vMax,2); + display.print("v"); + + display.setCursor(0,56); + display.print("Caida "); + display.print(dropPct,0); + display.print("%"); + + drawInvertedLabel(95,56,"B1>"); + + display.setCursor(70,56); + display.print("B2<"); + + display.display(); +} +void drawTestSummaryScreen_WithGraph_Compact( + uint8_t sel, + uint8_t finalCode, + float vAvg, + float vMin, + float vMax, + uint8_t dropPct +){ + display.clearDisplay(); + display.setTextColor(SSD1306_WHITE); + + const char *chipTxt = "CPU"; + switch(sel){ + case TCSEL_CPU: chipTxt="CPU"; break; + case TCSEL_GPU: chipTxt="GPU"; break; + case TCSEL_VRAM: chipTxt="VRAM"; break; + case TCSEL_BUCK: chipTxt="BUCK"; break; + } + +const char *resTxt = "OK"; +bool badRes = false; +switch(finalCode){ + case 0: resTxt="OK"; badRes=false; break; + case 1: resTxt="V BAJO"; badRes=true; break; + case 2: resTxt="V ALTO"; badRes=true; break; + case 3: resTxt="CORTO"; badRes=true; break; + case 5: resTxt="SIN CARGA"; badRes=true; break; + default: + case 4: resTxt="INESTABLE"; badRes=true; break; +} + + char headerBuf[20]; + snprintf(headerBuf,sizeof(headerBuf),"%s %s", chipTxt, resTxt); + + display.setTextSize(1); + int16_t hbX, hbY; uint16_t hbW, hbH; + display.getTextBounds(headerBuf, 0,0, &hbX,&hbY,&hbW,&hbH); + + int16_t boxX = (OLED_W - (int16_t)hbW - 6) / 2; + if(boxX < 0) boxX = 0; + int16_t boxY = 0; + uint16_t boxW = hbW + 6; + uint16_t boxH = hbH + 4; + + display.fillRect(boxX, boxY, boxW, boxH, SSD1306_WHITE); + + display.setTextColor(SSD1306_BLACK); + display.setCursor(boxX+3, boxY+2); + display.print(headerBuf); + + display.setTextColor(SSD1306_WHITE); + +const int GXg = 0; +const int GW = 128; +const int yTop = 16; +const int y1V = 24; +const int y05V = 36; +const int y0V = 52; +const int yText0 = 10; + +display.setTextSize(1); +display.setTextColor(SSD1306_WHITE); +display.setCursor(0, yText0); +display.print("1v"); + +display.setCursor(0, y05V-3); +display.print("0.5"); + +display.setCursor(0, y0V-3); +display.print("0v"); + +for (int x = 12; x < GW; x++) { + if ((x % 4) < 2) { + display.drawPixel(GXg + x, y1V, SSD1306_WHITE); + } +} + +for (int x = 12; x < GW; x++) { + if ((x % 4) < 2) { + display.drawPixel(GXg + x, y05V, SSD1306_WHITE); + } +} + +for (int x = 12; x < GW; x++) { + display.drawPixel(GXg + x, y0V, SSD1306_WHITE); +} + +if (tc_traceCount > 1) { + + uint16_t startIdx = (uint16_t)(tc_traceCount * 20UL / 100UL); + if (startIdx >= tc_traceCount - 1) startIdx = 0; + + uint16_t usedCount = tc_traceCount - startIdx; + if (usedCount < 2) usedCount = 2; + + const float V_TOP = 1.00f; + const float V_MID = 0.50f; + + const int yTopTrace = y1V; + const int yBottomTrace = y05V; + + int prevX = -1; + int prevY = -1; + + for (int x = 12; x < GW; x++) { + + uint16_t idx = startIdx + (uint16_t)( + ((uint32_t)(x-12) * (uint32_t)usedCount) / (uint32_t)(GW-12) + ); + if (idx >= tc_traceCount) idx = tc_traceCount - 1; + + float vNow = tc_trace[idx]; + + if (vNow > V_TOP) vNow = V_TOP; + if (vNow < V_MID) vNow = V_MID; + + float norm = (vNow - V_MID) / (V_TOP - V_MID); + + int yPix = yBottomTrace + - (int)( norm * (float)(yBottomTrace - yTopTrace) + 0.5f ); + + if (yPix < yTopTrace) yPix = yTopTrace; + if (yPix > yBottomTrace) yPix = yBottomTrace; + + int Xpix = GXg + x; + display.drawPixel(Xpix, yPix, SSD1306_WHITE); + + if (prevX >= 0) { + if (abs(yPix - prevY) > 1) { + int yA = (yPix < prevY) ? yPix : prevY; + int yB = (yPix < prevY) ? prevY : yPix; + for (int yy = yA; yy <= yB; yy++) { + display.drawPixel(Xpix, yy, SSD1306_WHITE); + } + } + } + + prevX = Xpix; + prevY = yPix; + } + +} else { + + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + display.setCursor(32, (y1V + y05V)/2 - 3); + display.print("SIN DATOS"); +} + +char footerBuf[48]; + +int vMax_i = (int)floor(fabs(vMax)); +int vMax_d = (int)round((fabs(vMax) - (float)vMax_i) * 100.0f); +if (vMax_d > 99) vMax_d = 99; + +int vMin_i = (int)floor(fabs(vMin)); +int vMin_d = (int)round((fabs(vMin) - (float)vMin_i) * 100.0f); +if (vMin_d > 99) vMin_d = 99; + +int vAvg_i = (int)floor(fabs(vAvg)); +int vAvg_d = (int)round((fabs(vAvg) - (float)vAvg_i) * 100.0f); +if (vAvg_d > 99) vAvg_d = 99; + +snprintf( + footerBuf, + sizeof(footerBuf), + "%02u%% +%d.%02d -%d.%02d =%d.%02d", + (unsigned int)dropPct, + vMax_i, vMax_d, + vMin_i, vMin_d, + vAvg_i, vAvg_d +); + +int16_t fx, fy; +uint16_t fW, fH; +display.getTextBounds(footerBuf, 0, 0, &fx, &fy, &fW, &fH); + +int16_t footerY = OLED_H - fH; +int16_t footerX = (OLED_W - fW) / 2; +if (footerX < 0) footerX = 0; + +display.setCursor(footerX, footerY); +display.print(footerBuf); + + display.display(); +} + +TestChipResult_TC classifyResult_TC(){ + + uint16_t useCnt = (tc_cntStable > 0) ? tc_cntStable : tc_cnt; + float useVmin = (tc_cntStable > 0) ? tc_vMinStable : tc_vMin; + float useVmax = (tc_cntStable > 0) ? tc_vMaxStable : tc_vMax; + + if(useCnt == 0){ + return TCR_FAIL; + } + + float vAvg = tc_vSum / (float)tc_cnt; + float aAvg = tc_aSum / (float)tc_cnt; + +if (testChipSel_TC == TCSEL_GPU) { + if (aAvg < 0.02f) return TCR_NOCON; +} else { + if (aAvg < 0.05f) return TCR_NOCON; +} + + if ((useVmin < 0.60f && aAvg > 0.5f) || (useVmin < 0.35f)) { + return TCR_SHORT; +} + + if(useVmax > 1.40f){ + return TCR_VHIGH; + } + + bool vOk = (vAvg >= 0.80f && vAvg <= 1.20f); + bool aOk = (aAvg >= 0.20f && aAvg <= 3.50f); + if(vOk && aOk){ + return TCR_OK; + } + + if(vAvg < 0.80f){ + return TCR_VLOW; + } + + if(vAvg > 1.20f){ + return TCR_VHIGH; + } + + return TCR_FAIL; +} + +const float CPU_SHORT_V = 0.30f; +const float CPU_MIN_AVG_OK_V = 0.75f; +const float CPU_MAX_EXPECTED_V = 1.20f; +const float CPU_DROP_BAD_PCT = 30.0f; + +TestChipResult_TC classifyCPU_TC(){ + if(tc_cnt == 0){ + + return TCR_FAIL; + } + + float vAvg = tc_vSum / (float)tc_cnt; + + float dropPct = 0.0f; + if (tc_vMax > 0.001f) { + dropPct = (tc_vMax - tc_vMin) / tc_vMax * 100.0f; + if(dropPct < 0.0f) dropPct = 0.0f; + } + + if (tc_vMin < CPU_SHORT_V){ + return TCR_SHORT; + } + + if (tc_vMax > CPU_MAX_EXPECTED_V){ + return TCR_VHIGH; + } + + if (vAvg < CPU_MIN_AVG_OK_V){ + return TCR_VLOW; + } + + if (dropPct > CPU_DROP_BAD_PCT){ + return TCR_FAIL; + } + + return TCR_OK; +} +void drawMainMenu(uint8_t sel){ + display.clearDisplay(); + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + + if(sel==MM_CANAL2){ + drawInvertedLabel(4,10,"CANAL 2"); + } else { + display.setCursor(4,10); + display.print("CANAL 2"); + } + + if(sel==MM_TESTCHIP){ + drawInvertedLabel(70,10,"TEST CHIP"); + } else { + display.setCursor(70,10); + display.print("TEST CHIP"); + } + + if(sel==MM_MEDICION){ + drawInvertedLabel(4,30,"MEDICION"); + } else { + display.setCursor(4,30); + display.print("MEDICION"); + } + + if(sel==MM_LAPTOP){ + drawInvertedLabel(70,30,"PORTATIL"); + } else { + display.setCursor(70,30); + display.print("PORTATIL"); + } + + drawInvertedLabel(4,52,"B1>"); + display.setCursor(50,52); + display.print("B2 pasa/sal"); + + display.display(); +} + +void drawTestChipMenu(uint8_t sel){ + display.clearDisplay(); + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + + if(sel==TC_CPU){ + drawInvertedLabel(4,8,"CPU"); + } else { + display.setCursor(4,8); display.print("CPU"); + } + + if(sel==TC_GPU){ + drawInvertedLabel(74,8,"GPU"); + } else { + display.setCursor(74,8); display.print("GPU"); + } + + if(sel==TC_VRAM){ + drawInvertedLabel(4,24,"VRAM"); + } else { + display.setCursor(4,24); display.print("VRAM"); + } + + if(sel==TC_BUCK){ + drawInvertedLabel(74,24,"BUCK"); + } else { + display.setCursor(74,24); display.print("BUCK"); + } + + display.setCursor(4,44); + display.print("Ajuste 1v Pulse B1"); + + display.setCursor(4,56); + display.print("Volver B2 largo"); + + display.display(); +} + +void drawVoltimetro(float vShow, const char *estadoTxt){ + display.clearDisplay(); + + display.setTextColor(SSD1306_WHITE); + display.setTextSize(2); + display.setCursor(2,0); + display.print("VOLTIMETRO"); + + { + char buf[16]; + dtostrf(vShow,0,3,buf); + int16_t x1,y1; uint16_t w1,h1; + display.setTextSize(2); + display.setTextColor(SSD1306_WHITE); + display.getTextBounds(buf,0,0,&x1,&y1,&w1,&h1); + int16_t xx=(OLED_W - (w1+12))/2; + if(xx<0)xx=0; + int16_t yy=24; + display.setCursor(xx,yy); + display.print(buf); + display.print("V"); + } + + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + display.setCursor(10,50); + display.print("Senal "); + display.print(estadoTxt); + + drawInvertedLabel(90,50,"B2<"); + + display.display(); +} + +void drawVoltimetroExitWarning(){ + display.clearDisplay(); + display.fillRect(0,0,OLED_W,OLED_H,SSD1306_WHITE); + + display.setTextColor(SSD1306_BLACK); + display.setTextSize(2); + display.setCursor(16,12); + display.print("RECUERDE"); + + display.setTextSize(1); + display.setCursor(28,36); + display.print("¡DESCONECTE"); + display.setCursor(28,48); + display.print("LAS PINZAS!"); + + display.display(); +} + +void drawLaptopScreen(bool needAdj19v){ + display.clearDisplay(); + display.setTextColor(SSD1306_WHITE); + + display.setTextSize(2); + display.setCursor(20,0); + display.print("PORTATIL"); + + display.setTextSize(1); + + if (needAdj19v) { + + display.setCursor(15, 20); + display.print("AJUSTE EL VOLTAJE"); + + drawInvertedLabel(22, 36, "RANGO 18 A 21V"); + + display.setCursor(4, 54); + drawInvertedLabel(4,54, "B1>"); + display.setCursor(105, 54); + display.print("B2<"); + } else { + + display.setCursor(4, 16); + display.print("ANALISIS"); + + display.setTextSize(2); + display.setCursor(0, 30); + display.print("0.000A"); + + display.setTextSize(1); + display.setCursor(4, 54); + display.print("B1 on/off"); + display.setCursor(90, 54); + display.print("B2<"); + } + + display.display(); +} + +void drawLaptopAdjustWarning(float vAvg){ + display.clearDisplay(); + + display.setTextSize(2); + display.setTextColor(SSD1306_WHITE); + display.setCursor(0, 0); + display.print("AVISO"); + + display.setTextSize(1); + display.setCursor(0, 22); + display.print("Medido: "); + display.print(vAvg, 2); + display.print("V"); + + display.fillRect(0, 36, 128, 14, SSD1306_WHITE); + display.setTextColor(SSD1306_BLACK); + display.setCursor(8, 38); + display.print("Ajuste 18 a 21V primero"); + + display.setTextColor(SSD1306_WHITE); + display.setCursor(92, 54); + display.print("B2<"); + + display.display(); +} + +void armCh1(){ + relay1=true; + setRelay(RELAY1,true); + + safeModeEnabled=true; + ch1OnMs=millis(); + vref = readVoltageCal(); + + lastShortTrip=false; + tripScreenDrawn=false; + + ch1State=CH1_ACTIVE; +} + +void forceGoHome(){ + relay1=false; + setRelay(RELAY1,false); + ch1State = CH1_IDLE; + lastShortTrip=false; + tripScreenDrawn=false; + uiMode = MODE_HOME; +} + +void redrawCurrentScreenAfterWake() { + uiMode = MODE_HOME; + ch1State = CH1_IDLE; + relay1 = false; + setRelay(RELAY1,false); + drawHomeIdle(); +} + +bool tcCheckOneVolt_OK() { + + relay1 = true; + setRelay(RELAY1, true); + safeModeEnabled = true; + ch1OnMs = millis(); + + delay(50); + + float vNow = 0.0f, aNow = 0.0f; + readVA(vNow, aNow); + +if (aNow >= 4.0f) { + relay1 = false; + setRelay(RELAY1, false); + beepCount(3); + lastResult_TC = TCR_SHORT; + testPhase_TC = TCPH_RESULT; + return false; +} + + tc_lastCheckVolt = vNow; + + relay1 = false; + setRelay(RELAY1, false); + + if (vNow >= TC_OK_MIN_V && vNow <= TC_OK_MAX_V) { + return true; + } else { + return false; + } +} + +struct TCProfile { + float minV; + float maxV; + float shortV; + float highV; + float dropBadPct; + unsigned long windowMs; +}; + +TCProfile tcProfile; + +void configureProfileForCurrentSelection(uint8_t sel) { + switch (sel) { + case TCSEL_CPU: + tcProfile = {0.80f, 1.20f, 0.30f, 1.40f, 30.0f, 5500}; + break; + + case TCSEL_GPU: + tcProfile = {0.80f, 1.20f, 0.30f, 1.50f, 25.0f, 5500}; + break; + + case TCSEL_VRAM: + tcProfile = {1.00f, 1.40f, 0.50f, 1.60f, 20.0f, 3000}; + break; + + case TCSEL_BUCK: + tcProfile = {0.60f, 2.00f, 0.20f, 2.50f, 50.0f, 500}; + break; + + default: + tcProfile = {0.80f, 1.20f, 0.30f, 1.40f, 30.0f, 5500}; + break; + } + + TC_MEASURE_WINDOW_MS = tcProfile.windowMs; +} + +void handleTestChip_TC(){ + + if(checkLongPressB2()){ + beepOnce(40); + + setRelay(RELAY1,false); + relay1=false; + + ch1State = CH1_IDLE; + uiMode = MODE_HOME; + return; + } + + switch(testPhase_TC){ + +case TCPH_READY: { + + drawTestChipMenu_TC(testChipSel_TC, lastResult_TC); + display.display(); + + if (consumeClick(BTN2)) { + beepOnce(40); + testChipSel_TC = (testChipSel_TC + 1) % TCSEL_COUNT; + drawTestChipMenu_TC(testChipSel_TC, lastResult_TC); + display.display(); + registerActivity(); + break; + } + + if (consumeClick(BTN1)) { + + relay1 = true; + setRelay(RELAY1, true); + delay(100); + + float vNow = 0.0f, aNow = 0.0f; + readVA(vNow, aNow); +tc_lastAdjV = vNow; +tc_lastAdjA = aNow; + delay(200); + relay1 = false; + setRelay(RELAY1, false); + + float vTarget = 1.0f; + if (testChipSel_TC == TCSEL_VRAM) { + vTarget = 1.20f; + } + + float vMinOk = vTarget * 0.90f; + float vMaxOk = vTarget * 1.10f; + + if (vNow >= vMinOk && vNow <= vMaxOk) { + + switch (testChipSel_TC) { + case TCSEL_GPU: tc_runMs = 6000; tc_settleMs = 300; break; + case TCSEL_VRAM: tc_runMs = 4000; tc_settleMs = 200; break; + case TCSEL_BUCK: tc_runMs = 300; tc_settleMs = 100; break; + default: tc_runMs = 4000; tc_settleMs = 200; break; + } + beepOnce(80); + testPhase_TC = TCPH_ARMED; + break; + } + + beepCount(2, 60, 80); + display.clearDisplay(); + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + display.setCursor(30,0); + display.print("TESTER CHIP"); + + if (testChipSel_TC == TCSEL_VRAM) { + drawInvertedLabel(4,48,"AJUSTE 1.2V PRIMERO"); + display.setCursor(4,28); +display.print("Medido: "); +display.print(tc_lastAdjV, 3); +display.print("V"); + } else { + drawInvertedLabel(4,48,"AJUSTE 1V PRIMERO"); + display.setCursor(4,28); +display.print("Medido: "); +display.print(tc_lastAdjV, 3); +display.print("V"); + } + + display.display(); + display.display(); +delaySafe(6000); +break; + break; +} + +} break; + +case TCPH_ARMED: { + + drawTestArmed_TC(testChipSel_TC); + + if (consumeClick(BTN2)) { + beepOnce(40); + testPhase_TC = TCPH_READY; + return; + } + +if(consumeClick(BTN1)){ + beepOnce(80); + + tc_vMin = 99.0f; + tc_vMax = 0.0f; + tc_vSum = 0.0f; + tc_cnt = 0; + tc_aMinStable = 99.0f; +tc_aMaxStable = 0.0f; + + tc_vMinStable = 99.0f; + tc_vMaxStable = 0.0f; + tc_aSumStable = 0.0f; + tc_cntStable = 0; + tc_aPrev = NAN; +tc_aP2P = 0.0f; +tc_aStepMax = 0.0f; + + for (uint16_t i = 0; i < TC_TRACE_MAX; i++) { + tc_trace[i] = 0.0f; + } + tc_traceCount = 0; + + relay1=true; + setRelay(RELAY1,true); + safeModeEnabled=true; + ch1OnMs=millis(); + + tcMeasureStartMs=millis(); + + testPhase_TC = TCPH_MEASURE; + return; +} +configureProfileForCurrentSelection(testChipSel_TC); + +} break; +case TCPH_MEASURE: { + + const unsigned long WARMUP_IGNORE_MS = 50; + const unsigned long STABLE_START_MS = 200; + + unsigned long now = millis(); + unsigned long elapsed = now - tcMeasureStartMs; + + float vNow = 0.0f; + float aNow = 0.0f; + readVA(vNow, aNow); + +if (elapsed < (tcMeasureStartMs + (TC_MEASURE_WINDOW_MS / 2))) { + tc_aSumEarly += aNow; + tc_aCntEarly++; +} else { + tc_aSumLate += aNow; + tc_aCntLate++; +} + +if (aNow > tc_aMax) tc_aMax = aNow; +if (aNow < tc_aMin) tc_aMin = aNow; + +if (!isnan(tc_aPrev)) { + float step = fabs(aNow - tc_aPrev); + if (step > tc_aStepMax) tc_aStepMax = step; +} +tc_aPrev = aNow; + +if (tc_traceCount < TC_TRACE_MAX) { + tc_trace[tc_traceCount] = vNow; + tc_traceCount++; +} + + if (elapsed >= WARMUP_IGNORE_MS){ + + if (vNow < tc_vMin) tc_vMin = vNow; + if (vNow > tc_vMax) tc_vMax = vNow; + tc_vSum += vNow; + + if (aNow < tc_aMin) tc_aMin = aNow; + if (aNow > tc_aMax) tc_aMax = aNow; + tc_aSum += aNow; +if (aNow < tc_aMinStable) tc_aMinStable = aNow; +if (aNow > tc_aMaxStable) tc_aMaxStable = aNow; + tc_cnt++; + } + +if (elapsed >= STABLE_START_MS){ + if (vNow < tc_vMinStable) tc_vMinStable = vNow; + if (vNow > tc_vMaxStable) tc_vMaxStable = vNow; + tc_aSumStable += aNow; + tc_cntStable++; + + if (tc_stableStartMs == 0) tc_stableStartMs = millis(); + + unsigned long stableMs = millis() - tc_stableStartMs; + const unsigned long STABLE_SPLIT_MS = 400; + + if (stableMs < STABLE_SPLIT_MS) { + tc_aSumEarly += aNow; tc_aCntEarly++; + } else { + tc_aSumLate += aNow; tc_aCntLate++; + } +} + +if (safeModeEnabled){ + if ((now - ch1OnMs) >= SAFE_ARM_DELAY_MS){ + if (vNow < TC_SHORT_V){ + relay1 = false; + setRelay(RELAY1,false); + beepCount(3); + + lastResult_TC = TCR_SHORT; + tc_finalCode = 3; + + testPhase_TC = TCPH_RESULT; + return; + } + } +} + + drawTestMeasuring_TC(testChipSel_TC); + + if (elapsed >= TC_MEASURE_WINDOW_MS){ + + relay1 = false; + setRelay(RELAY1,false); + +if (testChipSel_TC == TCSEL_CPU || testChipSel_TC == TCSEL_GPU) { + + uint16_t useCnt = (tc_cntStable > 0) ? tc_cntStable : tc_cnt; + float useVmin = (tc_cntStable > 0) ? tc_vMinStable : tc_vMin; + float useVmax = (tc_cntStable > 0) ? tc_vMaxStable : tc_vMax; + + float vAvg_all = (tc_cnt>0) ? (tc_vSum / (float)tc_cnt) : 0.0f; + float aAvg_all = (tc_cnt>0) ? (tc_aSum / (float)tc_cnt) : 0.0f; + +float aEarly = (tc_aCntEarly>0) ? (tc_aSumEarly/(float)tc_aCntEarly) : 0.0f; +float aLate = (tc_aCntLate >0) ? (tc_aSumLate /(float)tc_aCntLate ) : aEarly; + +if (aEarly >= 0.30f && aLate <= 0.05f && useVmin >= tcProfile.minV*0.95f) { + lastResult_TC = TCR_FAIL; +} + +else if (tc_aStepMax >= 0.50f) { + lastResult_TC = TCR_FAIL; +} + + float dropPct = 0.0f; + if (useVmax > 0.001f) { + dropPct = (useVmax - useVmin) / useVmax * 100.0f; + if (dropPct < 0.0f) dropPct = 0.0f; + if (dropPct > 99.0f) dropPct = 99.0f; + } + +float aEarlyCPU = (tc_aCntEarly > 0) ? (tc_aSumEarly / (float)tc_aCntEarly) : 0.0f; +float aLateCPU = (tc_aCntLate > 0) ? (tc_aSumLate / (float)tc_aCntLate ) : 0.0f; +float aDropCPU = aEarlyCPU - aLateCPU; +float aP2P_CPU = tc_aMax - tc_aMin; + +const float EARLY_MIN_A = 0.20f; +const float LATE_NEAR_ZERO = 0.05f; +const float DROP_MIN_A = 0.15f; +const float STEP_BAD_A = 0.35f; +const float P2P_BAD_A = 0.45f; +const float DROP_V_HELP = 10.0f; + +if ( (aEarlyCPU >= EARLY_MIN_A && aLateCPU <= LATE_NEAR_ZERO) || + (aDropCPU >= DROP_MIN_A) || + (tc_aStepMax >= STEP_BAD_A) || + (aP2P_CPU >= P2P_BAD_A && dropPct >= DROP_V_HELP) ) { + lastResult_TC = TCR_FAIL; +} + + const float NO_LOAD_A = 0.02f; + const float MIN_A_CPU = 0.30f; + const float MIN_A_GPU = 0.50f; + const float MIN_A_OK = (testChipSel_TC==TCSEL_GPU) ? MIN_A_GPU : MIN_A_CPU; + +if (aAvg_all <= 0.005f) { + lastResult_TC = TCR_NOCON; +} +else if ((useVmin < 0.60f && aAvg_all > 0.5f) || (useVmin < 0.35f)) { + lastResult_TC = TCR_SHORT; +} + else if (useVmax > tcProfile.highV) { + lastResult_TC = TCR_VHIGH; + } + else if (vAvg_all < tcProfile.minV) { + lastResult_TC = TCR_VLOW; + } + else if (vAvg_all > tcProfile.maxV) { + lastResult_TC = TCR_VHIGH; + } + else if (aAvg_all < MIN_A_OK) { + lastResult_TC = TCR_FAIL; + } + else if (dropPct > tcProfile.dropBadPct) { + lastResult_TC = TCR_FAIL; + } + else { + lastResult_TC = TCR_OK; + } + +{ + float aP2P = tc_aMax - tc_aMin; + float rel = (aAvg_all > 0.0f) ? (aP2P / aAvg_all) : 0.0f; + if (rel > 0.55f && dropPct > 6.0f) { + lastResult_TC = TCR_FAIL; + } +} +} +else if (testChipSel_TC == TCSEL_VRAM) { + + float vAvg_all = (tc_cnt>0) ? (tc_vSum/(float)tc_cnt) : 0.0f; + float aAvg_all = (tc_cnt>0) ? (tc_aSum/(float)tc_cnt) : 0.0f; + +if (aAvg_all < 0.02f) lastResult_TC = TCR_NOCON; + else if (tc_vMin < 0.50f) lastResult_TC = TCR_SHORT; + else if (tc_vMax > 1.60f) lastResult_TC = TCR_VHIGH; + else if (vAvg_all < 1.10f) lastResult_TC = TCR_VLOW; + else if (vAvg_all > 1.30f) lastResult_TC = TCR_VHIGH; + else lastResult_TC = TCR_OK; +} + +else { + uint16_t useCnt = (tc_cntStable > 0) ? tc_cntStable : tc_cnt; + float useVmin = (tc_cntStable > 0) ? tc_vMinStable : tc_vMin; + float useVmax = (tc_cntStable > 0) ? tc_vMaxStable : tc_vMax; + + float vAvg_all = (tc_cnt > 0) ? (tc_vSum / (float)tc_cnt) : 0.0f; + float aAvg_all = (tc_cnt > 0) ? (tc_aSum / (float)tc_cnt) : 0.0f; + float aP2P = tc_aMax - tc_aMin; + + float dropPct = 0.0f; + if (useVmax > 0.001f) { + dropPct = (useVmax - useVmin) / useVmax * 100.0f; + if (dropPct < 0.0f) dropPct = 0.0f; + if (dropPct > 99.0f) dropPct = 99.0f; + } + + const float NOLOAD_A = 0.02f; + const float OK_V_MIN = 0.85f; + const float OK_V_MAX = 1.20f; + const float OK_DROP_MAX = 20.0f; + const float FLUCT_A_STRONG = 0.40f; + const float FLUCT_A_MED = 0.25f; + const float UNSTABLE_DROP = 25.0f; + const float SHORT_A_SPIKE = 3.0f; + const float SHORT_V_ABS = 0.60f; + + if (aAvg_all < NOLOAD_A && dropPct < 3.0f) { + lastResult_TC = TCR_NOCON; + } + + else if ( (tc_aMax >= SHORT_A_SPIKE && useVmin < SHORT_V_ABS) || + (dropPct >= 55.0f && aAvg_all > 0.5f) ) { + lastResult_TC = TCR_SHORT; + } + + else if (aP2P >= FLUCT_A_STRONG) { + lastResult_TC = TCR_FAIL; + } + + else if (aP2P >= FLUCT_A_MED && dropPct >= UNSTABLE_DROP) { + lastResult_TC = TCR_FAIL; + } + + else if (vAvg_all >= OK_V_MIN && vAvg_all <= OK_V_MAX && dropPct <= OK_DROP_MAX) { + + if (aAvg_all >= 0.04f && aAvg_all <= 3.0f) { + lastResult_TC = TCR_OK; + } else { + + lastResult_TC = TCR_FAIL; + } + } + + else if (vAvg_all < OK_V_MIN) { + lastResult_TC = TCR_VLOW; + } + else if (vAvg_all > OK_V_MAX) { + lastResult_TC = TCR_VHIGH; + } + + else { + lastResult_TC = TCR_FAIL; + } +} + +if (tc_cntStable >= 12 && lastResult_TC != TCR_SHORT && lastResult_TC != TCR_NOCON) { + float aP2P_stable = tc_aMaxStable - tc_aMinStable; + + const float TH_CPU_GPU = 0.45f; + const float TH_VRAM_BUCK = 0.30f; + + bool isCpuGpu = (testChipSel_TC == TCSEL_CPU || testChipSel_TC == TCSEL_GPU); + float th = isCpuGpu ? TH_CPU_GPU : TH_VRAM_BUCK; + + float vAvg_all = (tc_cnt>0) ? (tc_vSum / (float)tc_cnt) : 0.0f; + if (vAvg_all >= 0.75f && vAvg_all <= 1.25f) { + if (aP2P_stable >= th) { + lastResult_TC = TCR_FAIL; + } + } +} + +switch(lastResult_TC){ + case TCR_OK: tc_finalCode = 0; break; + case TCR_VLOW: tc_finalCode = 1; break; + case TCR_VHIGH: tc_finalCode = 2; break; + case TCR_SHORT: tc_finalCode = 3; break; + case TCR_NOCON: tc_finalCode = 5; break; + default: tc_finalCode = 4; break; +} + + testPhase_TC = TCPH_RESULT; + return; + } + + } break; + + case TCPH_RESULT: { + + float vAvg = (tc_cnt>0) ? (tc_vSum/(float)tc_cnt) : 0.0f; + + float dropPctF = 0.0f; + if(tc_vMax>0.001f){ + dropPctF = (tc_vMax - tc_vMin)/tc_vMax * 100.0f; + if(dropPctF<0) dropPctF=0; + } + uint8_t dropPct = (dropPctF>99.0f)?99:(uint8_t)(dropPctF+0.5f); + + drawTestSummaryScreen_WithGraph_Compact( + testChipSel_TC, + tc_finalCode, + vAvg, + tc_vMin, + tc_vMax, + dropPct + ); + + if (consumeClick(BTN1)) { + beepOnce(80); + + tc_vMin = 99.0f; + tc_vMax = 0.0f; + tc_vSum = 0.0f; + tc_cnt = 0; + + tc_aMin = 99.0f; + tc_aMax = 0.0f; + tc_aSum = 0.0f; +tc_aMinStable = 99.0f; +tc_aMaxStable = 0.0f; + tc_vMinStable = 99.0f; + tc_vMaxStable = 0.0f; + tc_aSumStable = 0.0f; + tc_cntStable = 0; +tc_aPrev = NAN; +tc_aP2P = 0.0f; +tc_aStepMax = 0.0f; + for (uint16_t i = 0; i < TC_TRACE_MAX; i++) { + tc_trace[i] = 0.0f; + } + tc_traceCount = 0; + + lastResult_TC = TCR_NONE; + tc_finalCode = 4; + + relay1 = true; + setRelay(RELAY1, true); + safeModeEnabled = true; + ch1OnMs = millis(); + + tcMeasureStartMs = millis(); + + testPhase_TC = TCPH_MEASURE; + return; + } + + if (consumeClick(BTN2)) { + beepOnce(40); + + relay1 = false; + setRelay(RELAY1,false); + + tc_vMin = 99.0f; + tc_vMax = 0.0f; + tc_vSum = 0.0f; + tc_cnt = 0; +tc_aMinStable = 99.0f; +tc_aMaxStable = 0.0f; + tc_aMin = 99.0f; + tc_aMax = 0.0f; + tc_aSum = 0.0f; + + tc_vMinStable = 99.0f; + tc_vMaxStable = 0.0f; + tc_aSumStable = 0.0f; + tc_cntStable = 0; +tc_aPrev = NAN; +tc_aP2P = 0.0f; +tc_aStepMax = 0.0f; + for (uint16_t i = 0; i < TC_TRACE_MAX; i++) { + tc_trace[i] = 0.0f; + } + tc_traceCount = 0; + + lastResult_TC = TCR_NONE; + tc_finalCode = 4; + + testPhase_TC = TCPH_READY; + return; + } + +} break; + + } +} + +void handleLaptop_TC(){ + + if (laptopPhase == LAPH_ADJ) { + + if (consumeClick(BTN2)) { + beepOnce(40); + relay1 = false; setRelay(RELAY1, false); + ch1State = CH1_IDLE; + uiMode = MODE_MENU; + registerActivity(); + return; + } + + if (consumeClick(BTN1)) { + beepOnce(80); + + relay1 = true; setRelay(RELAY1, true); + safeModeEnabled = true; + ch1OnMs = millis(); + + unsigned long t0 = millis(); + float vSum = 0.0f; + uint16_t vCnt = 0; + float vNow = 0.0f, aNow = 0.0f; + + while (millis() - t0 < 1000) { + readVA(vNow, aNow); + vSum += vNow; + vCnt++; + delay(10); + } + + relay1 = false; setRelay(RELAY1, false); + + float vAvg = (vCnt ? (vSum / (float)vCnt) : 0.0f); + + if (vAvg < LAPTOP_VMIN || vAvg > LAPTOP_VMAX) { + display.clearDisplay(); + display.setTextColor(SSD1306_WHITE); + display.setTextSize(2); display.setCursor(15, 0); display.print("ATENCION"); + display.setTextSize(1); display.setCursor(18, 24); + display.print("Medido: "); display.print(vAvg, 2); display.print("V"); + drawInvertedLabel(17, 40, "AJUSTE 18 a 21V"); + display.display(); + display.display(); +delaySafe(3500); +return; + return; + } + + laptopPhase = LAPH_ANALYSIS; + registerActivity(); + return; + } + + drawLaptopScreen(true); + return; + } + + { + + if (consumeClick(BTN2)) { + beepOnce(40); + relay1 = false; setRelay(RELAY1, false); + ch1State = CH1_IDLE; + uiMode = MODE_MENU; + registerActivity(); + return; + } + + if (consumeClick(BTN1)) { + beepOnce(60); + relay1 = !relay1; + setRelay(RELAY1, relay1); + } + + float vNow = 0.0f, aNow = 0.0f; + readVA(vNow, aNow); + + if (relay1 && !lap_lastRelayOn) { + lap_vBase = vNow; + lap_aFast = aNow; + lap_aSlow = aNow; + lap_aSwingPeak = 0.0f; + } + lap_lastRelayOn = relay1; + + unsigned long nowMs = millis(); + if (nowMs - lap_lastSampleMs >= 30) { + lap_lastSampleMs = nowMs; + + lap_aFast += (aNow - lap_aFast) * 0.40f; + lap_aSlow += (aNow - lap_aSlow) * 0.05f; + float swing = fabs(lap_aFast - lap_aSlow); + + if (swing > lap_aSwingPeak) lap_aSwingPeak = swing; + else lap_aSwingPeak *= 0.92f; + + if (aNow >= L_STBY_MIN_A) { + lap_wasAboveStandby = true; + lap_lastAboveTs = nowMs; + } + } + + const char* diagMsg = "Analisis activo"; + + if (!relay1) { + diagMsg = "Analisis pausado"; + drawLaptopAnalysisSimple(diagMsg, aNow, relay1); + return; + } + + if (vNow > LAPTOP_VMAX || vNow < LAPTOP_VMIN) { + diagMsg = "V FUERA RANGO"; + drawLaptopAnalysisSimple(diagMsg, aNow, relay1); + return; + } + + if ( (aNow >= L_SHORT_I) || + (lap_vBase > 0.1f && vNow < (lap_vBase * (1.0f - L_SHORT_DROP_P)) && aNow > 0.10f) ) { + diagMsg = "CORTO!"; + drawLaptopAnalysisSimple(diagMsg, aNow, relay1); + return; + } + + if (aNow < L_NO_POWER_A) { + if (lap_wasAboveStandby && (nowMs - lap_lastAboveTs) < 1500) { + diagMsg = "SE APAGA"; + } else { + diagMsg = "SIN ALIMENTACION"; + } + drawLaptopAnalysisSimple(diagMsg, aNow, relay1); + return; + } + + if (aNow >= L_STBY_MIN_A && aNow <= L_STBY_MAX_A && lap_aSwingPeak < L_COMM_SWING_A) { + diagMsg = "STANDBY OK"; + drawLaptopAnalysisSimple(diagMsg, aNow, relay1); + return; + } + + if (lap_aSwingPeak >= L_COMM_SWING_A && aNow < L_BOOT_A) { + diagMsg = "COMUNICACION OK"; + drawLaptopAnalysisSimple(diagMsg, aNow, relay1); + return; + } + + if (aNow >= L_BOOT_A) { + diagMsg = "INTENTO ARRANQUE"; + drawLaptopAnalysisSimple(diagMsg, aNow, relay1); + return; + } + + diagMsg = "ALIMENTANDO..."; + drawLaptopAnalysisSimple(diagMsg, aNow, relay1); + return; + } +} + +void handleHomeMode(){ + if(checkLongPressB2()){ + beepOnce(40); + forceGoHome(); + registerActivity(); + return; + } + + switch(ch1State){ + + case CH1_IDLE:{ + if(consumeClick(BTN1)){ + if(!displaySleeping){ + beepOnce(50); + armCh1(); + registerActivity(); + return; + } + +if (consumeClick(BTN2)) { + beepOnce(40); + setRelay(RELAY1, false); + relay1 = false; + ch1State = CH1_IDLE; + uiMode = MODE_HOME; + registerActivity(); + return; +} + } + if(consumeClick(BTN2)){ + if(!displaySleeping){ + beepOnce(40); + uiMode=MODE_MENU; + registerActivity(); + return; + } + } + if(!displaySleeping){ + drawHomeIdle(); + } + delay(40); + } break; + + case CH1_ACTIVE:{ + if(consumeClick(BTN1)){ + if(!displaySleeping){ + beepOnce(50); + relay1=false; + setRelay(RELAY1,false); + ch1State=CH1_IDLE; + registerActivity(); + return; + } + } + + if (consumeClick(BTN2)){ + if(!displaySleeping){ + + safeMode = (SafeMode)((((int)safeMode) + 1) % 3); + + switch(safeMode){ + case SAFE_V: beepOnce(80); break; + case SAFE_OFF: beepOnce(20); break; + case SAFE_PLUS: beepCount(2,40,60); break; + } + + ch1OnMs = millis(); + registerActivity(); + } +} + + float vNow=0.0f, aNow=0.0f; + readVA(vNow,aNow); + +unsigned long nowMs = millis(); + +if (safeMode != SAFE_OFF){ + if ((nowMs - ch1OnMs) >= SAFE_ARM_DELAY_MS) { + if (vNow < SAFE_SHORT_VOLTAGE) { + relay1=false; setRelay(RELAY1,false); + lastTripWasPlus = false; + beepCount(3); + lastShortTrip=true; + ch1State=CH1_TRIPPED; + registerActivity(); + return; + } + } +} + +const float I_BASE = 0.06f; +const float I_SPIKE = 1.10f; +const float I_ABS_HARD = 1.50f; +const unsigned long I_ABS_CONFIRM_MS = 4; +const unsigned long I_IGN_START_MS = 6; +const float DI_DT = 0.80f; + +if (safeMode == SAFE_PLUS){ + if ((nowMs - ch1OnMs) >= (SAFE_ARM_DELAY_MS + I_IGN_START_MS)) { + aHist[aHidx] = aNow; + tHist[aHidx] = nowMs; + aHidx = (aHidx + 1) & 0x03; + if (!aHistPrimed && aHidx==0) aHistPrimed = true; + if (aHistPrimed){ + int i0 = (aHidx + 3) & 0x03; + int i1 = (aHidx + 2) & 0x03; + float a_now = aHist[i0]; + float a_prev = aHist[i1]; + unsigned long dt = tHist[i0] - tHist[i1]; + float di = a_now - a_prev; + bool jumpFast = ((a_now >= I_SPIKE) && (dt <= 40)) || ((di >= DI_DT) && (dt <= 40)); + bool absHigh = (a_now >= I_ABS_HARD); + static unsigned long absStartMs = 0; + if (absHigh){ + if (absStartMs == 0) absStartMs = nowMs; + } else { + absStartMs = 0; + } + bool absConfirmed = (absStartMs && (nowMs - absStartMs) >= I_ABS_CONFIRM_MS); + if (jumpFast || absConfirmed){ + relay1=false; setRelay(RELAY1,false); + lastTripWasPlus = true; + beepCount(3); + lastShortTrip=true; + ch1State=CH1_TRIPPED; + registerActivity(); + return; + } + } + } +} + + + static unsigned long diffStart=0; + unsigned long now=millis(); + float dvfabs=fabs(vNow - vref); + if(dvfabs > VREF_HYST){ + if(diffStart==0){ + diffStart=now; + } else if(now-diffStart>=VREF_REARM_HOLD_MS){ + vref=vNow; + diffStart=0; + } + } else { + diffStart=0; + } + + float dropV = vref - vNow; + if(dropV<0) dropV=0; + float pctDrop=(vref>0.001f)?(dropV/vref*100.0f):0.0f; + + if(pctDrop>=99.0f){ + beepOnce(60); + } + +float zoomFactor; +if (dropV < 0.10f) { + zoomFactor = 4.0f; +} else if (dropV < 0.20f) { + zoomFactor = 2.5f; +} else if (dropV < 0.40f) { + zoomFactor = 1.5f; +} else { + zoomFactor = 1.0f; +} + +float scaledDrop = dropV * zoomFactor; + +if(scaledDrop > GRAPH_MAX_DROP) scaledDrop = GRAPH_MAX_DROP; +if(scaledDrop < 0) scaledDrop = 0; + +float norm = scaledDrop / GRAPH_MAX_DROP; +uint8_t yVal = (uint8_t)(norm * (PH-1) + 0.5f); + +gbuf[head] = yVal; +head = (head+1) % PW; + + if(!displaySleeping){ + drawChannel1Frame(vNow,aNow,dropV,pctDrop,safeModeEnabled); + } + + delay(80); + } break; + + case CH1_TRIPPED: { + + if (consumeClick(BTN1)) { + if (!displaySleeping) { + beepOnce(50); + armCh1(); + registerActivity(); + return; + } + } + + if (consumeClick(BTN2)) { + if (!displaySleeping) { + beepOnce(40); + setRelay(RELAY1, false); + relay1 = false; + ch1State = CH1_IDLE; + uiMode = MODE_HOME; + registerActivity(); + return; + } + } + + if (!displaySleeping) { + drawTripScreen(); + } + + delay(40); + } break; + } +} + +void handleMainMenu(){ + if(checkLongPressB2()){ + beepOnce(40); + forceGoHome(); + registerActivity(); + return; + } + + if(consumeClick(BTN2)){ + if(!displaySleeping){ + beepOnce(40); + mainSel = (mainSel+1)%MM_COUNT; + registerActivity(); + } + } + + if(consumeClick(BTN1)){ + if(!displaySleeping){ + beepOnce(80); + registerActivity(); + + if(mainSel==MM_CANAL2){ + relay2 = !relay2; + setRelay(RELAY2, relay2); + forceGoHome(); + return; + } else if (mainSel == MM_TESTCHIP) { + + relay1 = false; + setRelay(RELAY1, false); + ch1State = CH1_IDLE; + + testChipSel = 0; + testChipSel_TC = TCSEL_CPU; + lastResult_TC = TCR_NONE; + testPhase_TC = TCPH_READY; + tc_vMin = 99.0f; + tc_vMax = 0.0f; + tc_vSum = 0.0f; + tc_cnt = 0; + tcMeasureStartMs = 0; + + safeModeEnabled = true; + ch1OnMs = millis(); + + uiMode = MODE_TEST_CHIP; + + registerActivity(); + drawTestChipMenu_TC(testChipSel_TC, lastResult_TC); + display.display(); + + return; + } + else if(mainSel==MM_LAPTOP){ + relay1=false; setRelay(RELAY1,false); + ch1State=CH1_IDLE; + laptopPhase = LAPH_ADJ; + uiMode=MODE_LAPTOP; + return; +} + + else if(mainSel==MM_MEDICION){ + relay1=false; setRelay(RELAY1,false); + ch1State=CH1_IDLE; + uiMode=MODE_VOLT; + return; + } else if(mainSel==MM_LAPTOP){ + relay1=false; setRelay(RELAY1,false); + ch1State=CH1_IDLE; + uiMode=MODE_LAPTOP; + return; + } + } + } + + if(!displaySleeping){ + drawMainMenu(mainSel); + } + delay(60); +} + +void handleTestChipMenu(){ + if(checkLongPressB2()){ + beepOnce(40); + forceGoHome(); + registerActivity(); + return; + } + + if(consumeClick(BTN2)){ + if(!displaySleeping){ + beepOnce(40); + testChipSel = (testChipSel+1)%TC_COUNT; + registerActivity(); + } + } + + if(consumeClick(BTN1)){ + if(!displaySleeping){ + + beepOnce(80); + registerActivity(); + } + } + + if(!displaySleeping){ + drawTestChipMenu(testChipSel); + } + delay(60); +} + +void handleVoltimetro(){ + if (consumeClick(BTN2)){ + beepOnce(60); + drawVoltimetroExitWarning(); + beepLongWarning(); + registerActivity(); + forceGoHome(); + return; + } + static float vSlow=0.0f; + static float vFast=0.0f; + static float vPeakSwing=0.0f; + static unsigned long winStart=0; + static uint8_t phase=0; + static uint8_t lastState=255; + static float vShow=0.0f; + static uint8_t pendingState=255; + static unsigned long stateStart=0; + const float FLUCT_THRESH_V=0.020f; + const float PULSE_THRESH_V=0.250f; + const float DV_STEP=0.120f; + const float PULSE_RATE=0.25f; + const unsigned long STABLE_TIME_MS=1000UL; + if (phase==0){ + if (winStart==0){ + winStart=micros(); + vPeakSwing=0.0f; + vFast=0.0f; + vSlow=0.0f; + } + float vPrev=0.0f; + bool havePrev=false; + unsigned long n=0, spikes=0; + float vMin=999.0f, vMax=0.0f, vSum=0.0f; + while ((unsigned long)(micros()-winStart) < 30000UL){ + float vNow=0.0f, aDummy=0.0f; + readVA(vNow,aDummy); + if (vFast==0.0f && vSlow==0.0f){ vFast=vNow; vSlow=vNow; } + vFast += (vNow - vFast)*0.5f; + vSlow += (vNow - vSlow)*0.05f; + float swing=fabs(vFast - vSlow); + if (swing>vPeakSwing) vPeakSwing=swing; + if (!havePrev){ vPrev=vNow; havePrev=true; } + else { if (fabs(vNow - vPrev)>DV_STEP) spikes++; vPrev=vNow; } + if (vNowvMax) vMax=vNow; + vSum+=vNow; + n++; + } + float p2p=(vMax - vMin); + float rate=(n>0)?(float)spikes/(float)n:0.0f; + uint8_t state; + if ((p2p>PULSE_THRESH_V) && (rate>PULSE_RATE)) state=2; + else if (p2p>FLUCT_THRESH_V || rate>(PULSE_RATE*0.5f)) state=1; + else state=0; + unsigned long now=millis(); + if (state!=pendingState){ + pendingState=state; + stateStart=now; + } + if ((now - stateStart)>=STABLE_TIME_MS && state!=lastState){ + if (state==2) beepCount(3); + else if (state==1) beepCount(2); + else beepOnce(60); + lastState=state; + } + vShow=(n>0)?(vSum/(float)n):vSlow; + phase=1; + } else { + if(!displaySleeping){ + const char *estadoTxt; + if (lastState==2) estadoTxt="PULSANTE"; + else if (lastState==1) estadoTxt="FLUCTUANTE"; + else estadoTxt="ESTABLE"; + display.clearDisplay(); + display.setTextColor(SSD1306_WHITE); + display.setTextSize(2); + display.setCursor(8,0); + display.print("VOLTIMETRO"); + char buf[16]; + dtostrf(vShow,0,3,buf); + int16_t x1,y1; uint16_t w1,h1; + display.setTextSize(2); + display.setTextColor(SSD1306_WHITE); + display.getTextBounds(buf,0,0,&x1,&y1,&w1,&h1); + int16_t xx=(OLED_W - (w1+12))/2; if(xx<0)xx=0; + int16_t yy=24; + display.setCursor(xx,yy); + display.print(buf); + display.print("V"); + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + display.setCursor(10,50); + display.print("V "); + display.print(estadoTxt); + drawInvertedLabel(90,50,"B2<"); + display.display(); + } + phase=0; + winStart=0; + } +} + + +void handleLaptop(){ + if(checkLongPressB2()){ + beepOnce(40); + forceGoHome(); + registerActivity(); + return; + } + + bool needAdj19v = true; + + if(consumeClick(BTN1)){ + if(!displaySleeping){ + beepOnce(80); + registerActivity(); + + } + } + + if(!displaySleeping){ + drawLaptopScreen(needAdj19v); + } + + delay(60); +} + +void handleOledSleep(){ + unsigned long now = millis(); + + if(displaySleeping){ + if(rawBtnPressed()){ + oledOn(); + beepOnce(30); + redrawCurrentScreenAfterWake(); + wakeSwallowInputs = true; + registerActivity(); + } + return; +} + + if(uiMode == MODE_HOME && ch1State == CH1_IDLE){ + if((now - lastActivityMs) >= OLED_SLEEP_MS){ + oledOff(); + return; + } + } +} + +void setup(){ + ina.begin(); + ina.setMaxCurrentShunt(16.0f, 0.005f); + pinMode(RELAY1,OUTPUT); + pinMode(RELAY2,OUTPUT); + setRelay(RELAY1,false); relay1=false; + setRelay(RELAY2,false); relay2=false; + + pinMode(BTN1,INPUT_PULLUP); + pinMode(BTN2,INPUT_PULLUP); + + pinMode(BEEPER,OUTPUT); + noTone(BEEPER); + + Wire.begin(); + Wire.setClock(100000); + display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR); + display.setRotation(2); + display.clearDisplay(); + display.display(); + + ina.begin(); +wdt_enable(WDTO_2S); + vref = readVoltageCal(); + for(uint8_t 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 j{!lHPt