From ca0b52493fef4d0f147836942ec2f142b9120842 Mon Sep 17 00:00:00 2001 From: Edward Emelianov Date: Wed, 24 Sep 2025 23:36:46 +0300 Subject: [PATCH] Works for 5 sensors --- F3:F303/MLX90640multi/i2c.c | 7 +- F3:F303/MLX90640multi/main.c | 6 +- F3:F303/MLX90640multi/mlx90640.bin | Bin 24696 -> 15372 bytes F3:F303/MLX90640multi/mlx90640.c | 158 +++++++++----------- F3:F303/MLX90640multi/mlx90640.creator.user | 2 +- F3:F303/MLX90640multi/mlx90640.h | 4 +- F3:F303/MLX90640multi/mlxproc.c | 78 +++++++--- F3:F303/MLX90640multi/mlxproc.h | 3 +- F3:F303/MLX90640multi/proto.c | 155 +++++++++++++------ F3:F303/MLX90640multi/proto.h | 2 + F3:F303/MLX90640multi/usb_dev.h | 2 +- F3:F303/MLX90640multi/version.inc | 4 +- 12 files changed, 249 insertions(+), 172 deletions(-) diff --git a/F3:F303/MLX90640multi/i2c.c b/F3:F303/MLX90640multi/i2c.c index 7407783..32a115a 100644 --- a/F3:F303/MLX90640multi/i2c.c +++ b/F3:F303/MLX90640multi/i2c.c @@ -33,6 +33,7 @@ static volatile int I2Cbusy = 0, goterr = 0; // busy==1 when DMA active, goterr= static uint16_t I2Cbuf[I2C_BUFSIZE]; static uint16_t i2cbuflen = 0; // buffer for DMA rx and its len static volatile uint16_t dma_remain = 0; // remain bytes of DMA read/write +static uint8_t dmaaddr = 0; // address to continuous read by DMA // macros for I2C rx/tx #define DMARXCCR (DMA_CCR_MINC | DMA_CCR_TCIE | DMA_CCR_TEIE) @@ -271,6 +272,7 @@ static uint8_t dmard(uint8_t addr, uint16_t nbytes){ (void) I2C1->RXDR; // avoid wrong first byte DMA1_Channel7->CCR = DMARXCCR | DMA_CCR_EN; // init DMA before START sequence if(!i2c_startr(addr, nbytes, 1)) return 0; + dmaaddr = addr; dma_remain = nbytes > 255 ? nbytes - 255 : 0; // remainder after first read finish I2Cbusy = 1; return 1; @@ -329,7 +331,8 @@ void i2c_bufdudump(){ // get DMA buffer with conversion to little-endian (if transfer was for 16-bit) uint16_t *i2c_dma_getbuf(uint16_t *len){ - if(i2c_got_DMA) USND("DMA GOT!"); + //if(i2c_got_DMA) USND("DMA GOT!"); + //U("T="); U(u2str(Tms)); U("; cndtr: "); USND(u2str(DMA1_Channel7->CNDTR)); if(!i2c_got_DMA || i2cbuflen < 1) return NULL; i2c_got_DMA = 0; i2cbuflen >>= 1; // for hexdump16 - now buffer have uint16_t! @@ -358,7 +361,7 @@ static void I2C_isr(int rx){ uint16_t len = (dma_remain > 255) ? 255 : dma_remain; ch->CNDTR = len; if(rx){ - if(!i2c_startr(0, dma_remain, 0)){ + if(!i2c_startr(dmaaddr, dma_remain, 0)){ goterr = 1; goto ret; } ch->CMAR += 255; diff --git a/F3:F303/MLX90640multi/main.c b/F3:F303/MLX90640multi/main.c index f6697cc..52d1b9a 100644 --- a/F3:F303/MLX90640multi/main.c +++ b/F3:F303/MLX90640multi/main.c @@ -41,7 +41,7 @@ int main(void){ } USBPU_OFF(); hw_setup(); - i2c_setup(I2C_SPEED_100K); + i2c_setup(I2C_SPEED_400K); USB_setup(); USBPU_ON(); uint32_t ctr = Tms, Tlastima[N_SESORS] = {0}; @@ -49,6 +49,7 @@ int main(void){ while(1){ if(Tms - ctr > 499){ ctr = Tms; + if(!mlx_nactive()){ mlx_stop(); mlx_continue(); } pin_toggle(GPIOB, 1 << 1 | 1 << 0); // toggle LED @ PB0 } int l = USB_receivestr(inbuff, MAXSTRLEN); @@ -72,7 +73,8 @@ int main(void){ if(Tnow != Tlastima[i]){ fp_t *im = mlx_getimage(i); if(im){ - U("Timage="); USND(u2str(Tnow)); drawIma(im); + U(Sensno); USND(i2str(i)); + U(Timage); USND(u2str(Tnow)); drawIma(im); Tlastima[i] = Tnow; } } diff --git a/F3:F303/MLX90640multi/mlx90640.bin b/F3:F303/MLX90640multi/mlx90640.bin index 330aaa26ea6c3da926f0746699a33ec168e10c23..2145dfaa238c6f886bc51e58e855e9527d6d8f61 100755 GIT binary patch literal 15372 zcmdseYgAN6)@YqaKS6242clyRG-5*|wxC8Z2~7jXhDIS`MuW~wH-bFm(E=LMGhb{( z4N)_rU?z&m#HdL$nh1zrqQ-$}jQKLjOcQYiHS@tFPC5bmZNav`!%Br3$q66@#x! zW|Cz-ZF#cz7XocE-c7C!(AV=iD%WgX`T;@=G9x`&i}V4e=7t%?|Be=A!c?AqsBf>Z zuXFBqeCh`R()Y(c{^fQgaof`+L+_{S`%}WB&DR9fHzNZL*eMzDTaP}+&NE13rrmlu z?M6>MGTh*kx$R3OL;9y=L!C>@k;09hP7%~OiXr{e#6Ugum>F<{?EVY|V(bg_v$H*P zO0D@-`xQZMza+@_oCLl;^9}QbZ~1zX!M~_O)C+VQbD|FGQq-~dOtc9xGs{8?^2(l) zJlZ|mlvk#rCtq7r7MV%$LDY1DaNAQT;6_3rhB{Nic79TPz1TdcAj7s2tqn`3Lys<6 zdM(mpUs>s~uZZLW>AZpvfKHMGUYnE==|Kf)(1w^w56Z6-4xuy=BfvDXVDSZIMnEQp z>1W%+p?AR)`}G1@=Gp=x-0%IBA8A}&*`C0((@s1N4`hFuMsjS|oiMseHaVugWe>af z`XkF8$*yD5SbeOfLmzpx&B%tces8${*Z@s7w!NMfbM}3AauC@=wO~tVil>OnDIJNB>+CkmIdM5Et(cZ_KE zID=V{Rx0-m>cH|L9nvQof{>vhL`f`>Xv2DXzoqyo!LsSosf!JPMrHUB=hZe^8`PuB zxY&S4aDO>bd%$_IL1s)1e+iznaZ>nRCyWd5x%AECBqTqUh~ytY;m%feCS;$TV$pb#jNR74Ejfg+UVDVe$ErA~=_?vSi;Rfk<9*n$Zql*J;zba%(BR7p>Nq9Y@?cy01;9 zS=B?tt5`H!(Km%RXV2|Y0sfnAog|?ZW87+!#Zm*8rTq1+h6(0~uEi|VXV6ACCfA}^ zv!hq|q!ag*^g*v|z?Y!de#cR_PAVDLPATH(fmG_Np2BpphwEAxd;5ANza@1s8(`)? zurAHl-T-Ncc%*&I0W>0&eJD1&KaIY9bG{bKAn!=y33>*WLPE^N`~%G$4%`!|b7lsZ zzjQX$lJSGBn167eB<_gYLQlCaiG90gRpb=jntfz|ratbX1|(B3HKhH6sIemUh&x5< z?9oNuc0L-}??h@W&woOUJaFK^bZdKkOXxCU&jh!OCZbR{88KNP5=YKb(SeR%wwh-4 zw9%TU+dN$=V@w)Shg4H7R*)x@)#-jdGbRm3QB}1IE-DMBQ+zSPa8{a+*NQ z=bwG{*~Cnw-U@t}vpS$~HyJRN>e`jdyz&2&q?;Qd6PK&QAw>C(r`@8(N^sXn|ZygS}`sO@G%#EGE)aJ|bDB{is8= zgtFEdyj?s{rZT?}(0n4{BK%@L*lvlfXds248rGa+fI|bwZCx6dG0!wF6KO&_0@&c< zu;O4|UKzm?q^dp49FzH2SDPRYexYsSSyd|$zEubajx?h1Y<^3@bn`_aFgVSOa|i3v zaV&qd=DI&TkbT-*+X@h(QBVfk+x(h{>nXf{V~S_%{AtD%^Yvo?{Oe}iBiN{z@j}~S zcncFVnQUIZ(Ez-|%qftK5aAaEI{0HJ#@yW&&R%t<^rMs%9swo0?S{LoNQNJ zpm{$bXH^$4ozGxQ{L!#dr_8ypmXU1pid4F}Pr!fJZwYlbR*povIHAc_Y#d!T~ zDw!eGcXtcuNqOHB=BARH(g0kWzh;I6qIXlt8rk5r+|r2xvE)1FER#Rth6UlQrcU2a zr1lEd&gq${JOb*f+y|7Wz^vJr2DBNVW0Ci1M5=uv)q_CwCQ$v=4^)4fKG0=muQ~mR zFSPli$?IS@d%F`;r|wg>ugf)eI}z9iuk~0z{>uAUPXOv;V-CI{(_!I*7uya=YLCY|IpKk?JXU=_7Cun2R?yy z7S7NtTYRLrvv|MZ{f@z~DeTFPOJOrYt7csdJ7EZ7r{A1#R>KMx7KOcQP!}KTh-2f~ z&@@Zy+GI)#TO224Bao#Rs4t;lWu*5wReV_05wnL`K<=?)Eag~|%1{^s< zJGKdvp4|qJCyP_>{toB%kfY?91<^MV_$SWUuy;DXBNx7En982-Fcg0g<_dc!lxBn2 zDWC`TF2{Fh(iy|y0g61XREA;XJnr<44FVT_fUHjYJ$Sn2Fx^T*C~_9L*j4s@SgwzuVt zvxJ&<>yWyjgw;p*y~q8t3+P!v4*AVu_VqTu_eI|X(!0Qr8KS zqU)fxbN9tT1--BJPbHI*cGn@?Hg6y!j$uT$mki$D*zk=-^vP{MXvJ$sW9>FHEgj&yj&>B~UM^{_m z_kKD?&5A1s{`;-4N79t0tDpzc_ILTcR{sNR6zl8?6>ZBo{tk9~pj&6Fsyz_A=Ud&c zTPYT=AbG9h>kKg;#SC}hmXKB)anD1H8;`t*8DA5zT)je$eQV2pzEqw#{ghaeI>5GKRTNZ7?p>hc3bw|4_%}Ub8p!ydnGiSc1Mz`V}9?NdlTHX zFfMvl7tcUWoO>^VO^Xi=S3=t|c+UaoRCr%{FMM~qP^N1aDstKdD|%6g(h(7JSb`6t z$zj?9yfQx=q$cBckSh${!!e8;Cfu>g_W=U6u=jissuzXX`qV+0x)WAw74!ydrJPfR zy(mP&ILyhBy5&;DZ`>+7(CezUwF>&2AhuO7sN~S!nnCTgW)OR$12A0@AeVuUz-pSUF_;U$f^i+VDkXf71j9fso*b6meOnJujD@Y21WzT307#~^5#CGpvak~;$;Jup zWKz-y@T3XuIi$c9?|h&8WGCGyX(nEoRx_YQlXyJ7>XHl8zP zws6IHPL9d^`*)<|EzX`t59!P3q0Utk+^eK0oame6t9dNcM;wxHd)|h=NdouJJUFpf z0z6B3uE5r;JPkhaPp-}e9+A}8HmQy0it+$6=F*@+5yH!ZFsIOt<8Haa$-SO;X;3nQ z;)#iX(F`p>gSCCbZL=MeOoeRc_H82X3Rhoh>^R}p2f*xTCU`=b*-w&8^aS^Q<%+a% z8JsrfZ36nHmpZwvrJ&t8tP{_zw>der?RoC7?dWa7CJ~5%KXj4J#KDOf`gpRDYG&f8 zMrpG>fsEf}Aey9&!48rq6;UTlX(6EJI+(Na88~mVFSjKlW-Yw0or4%zbDLn*wF$)x zWNqPudY2y011t%6ctRxPX9wHXw1Guz52U*|NQGSCo-ds$H9{+Rl5UCwVCwN&l8W@s{u)BRQn~D;KT#D zr_{Z=%y6ed-_#bfVyChd;jctZI)|8O64J%{^PYYIa zlW#L~TG*`m7$gPz#ZYcJNvNCjSc3=t5um)n%_+q+0gK6o3{Hb*aWy%N?r1vj?0+uf z<3VB(^oe;``^zqQDV7>I`D%enZRgdtNni=wH)~K9i+RWne&~{wo)+|dr-iDVGSe{U zvSs(rdg78xYLgvo^4V0Uh362qL6h&-D)2sd{zld0`wtxry%FqTXz*!y0ufFOQ-h!Z zc&5`Pa92v-=RV4Oo_jNi7@j!zw(_ZrGa&zESS23#C-&fNTvW_^9Ll`Ry&1!EX?b6B zM4l9AbPf^;5_lT!l%=+Cg)mb-E(P9se_jbu0PM6UDT-5 zHz7v9xAmS9_V%CMW7r?@CIb8o5#9;#|8@pI{Z+C4KGgr}Tnlq!e4a1{SmTyKdCUs1 z-3ds(4a$Bfu(qUTWYxDop8MfDf>b4Cr?8K}KEGQCod4@qfCj%l5J*U(?_N&<>J=jO z7w#cu{4@;r>J?Dk5+h`i5z0Q&+#`hQZc3NqO!aoT^9st=PSdn+#@4Q z>;$(IRzu*~>G#$SBTbeA%}aD_K+&1e83)^TGJfq*L6xX7?=?pm9p>q9cHs|y((KoM zB+z@a`CY^|Az*Kh5Q&1#F+FTQWiarl%HXl*`?ZTZ{OT~{HbH6e!;Ck0&92k;HrQ&O4x~DzpKEfq!sEZw`NFUz1IW5dVd+gW7t13 zzWZow3uZA_kFWyGXEl%wj*&3>DTLbZ<hPh zsMNcQH^!O2vrI1y=eNMFtRZE3{`BH-k2NL5!=y|%XL}B%{Jr?P`RU>mzLR*`e4sc5 z+76h%E+NxWJ#ZeN`P$Mb)aEn_c3q>;plTHMm>Y%t`q|fKWhWIIJo_glnP*5+`z6)t z)Ku8XOv##@5t=!DS!%yjoppzhsx47wQ_0k zganzhA^B=8OA<G!KTeJ<}6*7vHZ;uDm;w+ll;;^YVf6jIzksu4y2IL zEUc8#Nc(*kn)hT^x*BSU_duT~Oud4qZ<@Fc71`kQmb&WFls38M+N_}I8v`it8OY<} z*dp$E+dYVSfSL5}2sI~+9g*we;f!8(cwi|h1={P=@4$&hXWOOJxQPCh+O)*8x(G@j zV#y^Z&k_ltuIeHpxEymb0fm67<;Z- zYkhInac9$wi(m<2&K_qgj0X{!>3m@ME#9hA4_ngMfz-)Et52GyBhf{-mON{N^+!7s3n;PWHwH_KC&BP)!cU4n*;>)&wO>Gs=%k_0b9e!q9}FavM% zhEEy>yJ*0{-lu_mSAQ3HNr!Mo!p?QkyY{%yPWwXWUlhYazJ+tHorm4Op_F|du4pIr z7GnowOgozmO2;7NEHJ_%^!vXz8(^K->FKh^zic2KD_o52E9YYFlV+_^yWH=+=4;_H zloaqhH|F>L*@wA*=Zk{5@CmdS#g=!VqNkAmD+IE8JD`?EO4^@B9UuZ6Y4chbQ9p!?gtQ-*(bUARr}Qo<>S zp!a)UKZ`nUflcp0xedhwrE*ez60JdZ_&U7%a4w(g3fz?ty1k`I5OfmpJor%ES0??P zZDuqg(fPga_>czizpwl6tX^Zg4>9dNQrGRGOS@e}Ot0%^<~|ph)a{Zd?Q_X8dtE)B zzUJzA8&>R?Y>y+K9Fa)yT7AZ+k2>R9IThzQXM8AXJYN~lfjH2OKCJQZo^+1;i@4q|4swdTUvqDq8vt!zgdLWaJ7N3WMcIltt!)GM*16QaeJ-WV z0~D}NYQAjYGHpmr57eV)0Jg_90X#Pgvh6vgdtAXb4JX^F;i7iFz{!<5POCg~KN`BZ z&Yj3TXdmMAq8GPv-$H%JUdxr)h=WQup`-?ONYA;MppRY1_5x2RCUph4iGcI&(4YpM zlq3E)>@H2yvWIsKcC?|U4_O!)<$E0Z6dmk#NlW*+6fp7=Z^#Ce(*JC$?^jaNJCos7 zh@j|!-GN`i?sK7UDns23Rnc(Q1QO>tTtsF+dabYi%)y#8+wtj9$=5cO&GxW{F<5|SL0UAf{jOL1(vCWG)x$OieYo_1EcaTL~ zgDnIss)309CU>Qo0ec*W_lIqg+WnA4ZlrgE4lw7kU>|%NsL5~--L-(f&lUK?I0b#V4|2i>qqfESvs0uR?p_!PghR!Hl(3#-Qo4P90)cCz4qf!=W z1|6}+F>kq_0X*{Fn77!kWX8PpehonL{fhzW9U1ffLWI`(iMfwD$hl!%;n~?9eL?$> zeap*mE;1XkILH<@>DXvCE-j#S%sb6rFys5K=|Dp-_FMz>g$q)M)6$1xIlk8l1DRhlkWEb+#c!xXwE)T6gAo%7PX-FMkp`YYrfiRuyd*R*sH0;r z`*$6KmA?xx1!n9rNWTckgxjqESj7Skz}dV4)}JN={RIjK$c!e#NEhM*1>{0~4|0o~ zCF6AW)Z=q0yYWKHF1VMV_vRHx@D!P6#&47Oz@PxfjtsgFb&S_&2i;7>?=;YjLsz$$ z!irIN1aGej1MINkMnZY**BKPxh86#wpszi^ynD4q#Dx4dejM{;q%7WGjNqq{27WTBG_N*eE^rrnpUX>xvfFaM?;hgezH3b5vKx33w_sYqyC4(nRKk@_pMNJ!`20J00tL79mxrTFQOm9h4GYMo3GQ+^ zV9ckQ4~Cr{8s1{(|FV4y_F?#p8EN(W<@#$vY|D8!3exkv+lh$&{NmE%r40tef3QP8 zcyznAwBDc}e0In8C5BnD0czqzxBrQxv@32x!Svz$*Ul{h&m{GT{uxsBfp39-B~K?1 zf}9xV!eUMg=goS6VIRgmjo|tFbi{wQT`@q)@Qtiedlz!T&O5Jcf8X#)M|7#oO+ZeD z$WJ;}gxX`!;HmBJ8fP3I^B%p2BK8>i^Yw!-Y=7)TXc`i>$G$3*F(?M{Z`ofJ%2g;D z@k!g#l5{cX&Ov+cx0Cp%;Tn7VU(x;NgZjUFUKa8HDE_K}%KZENpZtL$rl2KKS*j0C zTOUEu76%jUyRJ*5qu2I)0w>jWUmZk-gweXpmxMGOj^bB^Ptce*W)#Ps^wHnI9n)tE zkan2^k0LGZTJc?V%62?v2g4-Vq3bn3VUmbHcQmojFn)*qh~+dBJ$&7gI5m0>-0uhT z(@@mE`Ikt|r^a!3a@9<@=LLIO1)TMaZGRrddt`gS!7W<-9b5fs;CTJsQ}<2^ws?%b{G{WgTpA(xQX2pvg;>mjDi zA4QsLU4L~-j_V=HE)sdZyj>3-uy4EM5A&rTcfTa8JOuaHED>S26O;luuRuY>KIed% zA)qnfI2n-xw{WP%?>zy%-^JG2h^4wYLjgDWNIl*!2gh)@5kcU|zZ%}*)AD{QwO>I2 zl`JwM;CK$~*5h>V2(g`h@AE)sC#G|sMh^Rc%9!^W&`9?D<`h+HO5ftCJvGI+*-46; ze0C3Mws@?ktWK;6;giks-z`2h5$u(6PXQ~=^T;98&|}^&1iTjEG0wf0=bQ=tz8YdT z&h@eX<9#uX?+c*d*p5Wne-LRKP8menR|QNv=~VCi995f?b{SA5sLeB(%bd7ujkUs+!E$&QC1MJF3C%`@etjU1I0OXG&9{}Y1 zh+^pDJf(ZGQgOF?>055Q^gr+KXZ8cm$q~E;UK#l#>}Oz|lwm7%j;u=W?|K*dz6yOW z=1hi+e6#O_Q85&pH|A{`S;a;^is_xZPp1axG>CMLK+nOh!^XqQ$GlrcAe&81^igQk zd)+_geP-kjK(Pj(*89*u1GH@906HhW~c zI2vQ#+kk~eHbBqOVcZ@nwoe%$9z7sRI_ACU_qW0L4=<)|M2|c?{2?oO-GuOs02d)&Cs61#m>->=(y?P4(Tv>cjvo8YJs;AVz!+ z@hjQ$`N`$Wdxh>p6WpNzXifr~E6^L(z}JRthQ+MfK{WZji+y!0aWIEH3u{f7W6XOD zBpvliK=LEQSeic$KLPT;I;;a|JV5u0(9^?4h#=OP}J%kYg#!jp$0KtWKKLzxU^CKL@69TYtj91k>85rgBvKq=DR>%wdBhWr!x1Nm@| zn}}y{$nfHS_6k`|KcD$2i{>c$b{Hl)h)2Ug^QiiOgE4SRB86yD&sj7(pheBO}k z={+fV?Y$v;a);-}Ny+h1@8dut+0%V;YkIP$>!hSPch>14du4~`PbV>t#EJdFRZ z-giNte+BC9#mpEumV8MRBHP{LN(?Shp_L*eiUnXUy9oNa0Su6Qa<633$(SOu&}ABus-o zrnBR_a|w**r4tx};5$Mic*RQs_SzV*^d14nyb~BdP%j_#w!nUBhe+dfq1OpxGKx=V z51*d|v~2>67%}Rd2>ohAoZkt4@9*Kvb}`H|GoE`e=kVOaYw$Ms-ZTr(MK}vT3&jra z4NzL3tcLOmlyk<)=OF$IXYnd8`@%jGL+XM2=u#X(^!zt=0%|8pOG}#! z=Q}i|Tf+{99SlVfR}L7qctTG6G|qhX&s|1)#0;3Jo-w@v?zzvQh_{?C2~Sz=^M2v@ z^-S#jc;1iSs$9jlDq?O5i>$ZhoTfY?6n{r2=pbq)j(UH4Pg8j3)~I(UJPBR0$8~bU zbAGQI>qS(FU+gIQHkzi3U+hGSUnI?dJU0@)yO~Ab>8?ePk0K&`)cZ>@YJB*1k#W@9 z2>bIV>wTIrx*C91v^SH8pqJ)gy=g9J;C zNFiE<`Lrf%NjRCJbnGRFB%@woWCwgvGuxyoJl1hZaMa*+Y~`)Xup=8E%Vba3DSS5& z;naW?f+@qOq9bmGaJ6}58c?aSHRve`R?3zAXbx2uv+}- zxH23?Ot%Ic%d?JpUxgFni)SHYPt9R*&JG!S3uNq&{k}UK0eZKc-7QE|y9L^eM{&oP zZRRS}(bm{D!99;c;W9o5DKv1RAGoihUO=6zbQ9dMfejs@RvhuCO{3g?9pRwZekWcj z-ggoWlp4O+fE6OUKR6gx2{f@M;BCiBV#(kaNF8wOML`E^Lhcy$xeAbM)7gMytKu)N zd^d;*|I-i!g;2h*STY5L=E z5u33be&Lz2T6{8bbCQ;5V=8M`tD|NcBK5c}eZ>kTe&(hx&t0DVI9i#pECqK?U!Equ z;kIm(5wTXj+Ca& zIV)3EiZUooH9x+KV2lB{mNILxWxPp=4U>*F0C-MGZDAQxxqf|hVGU!oR9VUkYYMBX zlRyg7uWH0z;``<#M#Nud%@%Q?cPv1Z>#U|43&;S^%>Yx@6s;5at+S?N0Y~xORJ{_v zC{wTum>RsNu1pZyQdcf`fK!R#BFA-=RiHKKoRXHd0ym@lXKM->crr-{Ew5xO>uO3i z7c$j_71fnh)y(?JO%>x~#NszV{0Rp18XtpZEz|ib`sV zH|58zt1O>aSWxh6Vfno3n(~DBYD>Abtgw1sO;usxymCu5m}efFki^25-1ExIY7=#f z7U-D#O(kUo>O~6~Jrl2sUpQBnI5&QQl7YW+ONFJVu)MIM2CU3fmsAv$6*3tsnEcAJ zO3~H~$RiR_S}e?5W?iwRq6jd+^(+Mi*d3V~P?*tJnCjxnO=Sg4ej!s)SuwY|xMY1z zVF9z*QnsluJUl#Fxej-)tf(oe*i?wADhnzAQcz`ihN-A2W=hII5bWnExv3c$$|BrY z1f%|A+Z;<>#7W>gfi)$A!co3?UCAzq{1%Lt$v&kyT`3x9t ze4a4#bsJ*g2mdY*f9Ee_Dyx|IWsIh<)>>EvQ?CNN3WO~K5@jXTz~q09Wky=HvIdI= z+CIb76qZ9TOULjKnF_Qb)sjQd>vxL!z zVij7}6*Cou&y3qNTA3yqdQ*iIi ztC^Qy0xZET!Aq4HqT$zBDnwt%-&Cz!32dxYATG0RQ&koA{FUX^O2lSA1yN57VJJ5> zWjVw^R8d(2dX?1R`T2|mq@BPCaEL!_(9+Y%7VLu1yJrmF3m&ffufYE0GZGO3ltP2 zqu_yPng&(~QE;7(>o-E=KoOz7sR}7j#ed14f0p&Q@^43$R8FFjroH#%Vub9lGBGI# zePY0O=tw`4-3>X%9w_^v9D;HbiVpr5Mx^n7^`iqCk3m@tr5K7G%2cWxm7!YnD^!gN zQ3d=~BD^pKQt%(C;6s`?I2B$*EkjvoHA;l~BD4U0ng4;!pmJn^z7|vjG|ELfc;G{+ zLR0|%o<(}Vw*cihpuGvvl!De{4K8BKz~?%U6hHW*VTc-F?_z%dxc`(-1E$dS@Sg-K YOhjaq2A+QSTW|Nnr{940|M>5J0SqP8v;Y7A literal 24696 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 diff --git a/F3:F303/MLX90640multi/mlx90640.c b/F3:F303/MLX90640multi/mlx90640.c index 1acf9b2..365dc50 100644 --- a/F3:F303/MLX90640multi/mlx90640.c +++ b/F3:F303/MLX90640multi/mlx90640.c @@ -31,7 +31,10 @@ // tolerance of floating point comparison #define FP_TOLERANCE (1e-3) +// 3072 bytes static fp_t mlx_image[MLX_PIXNO] = {0}; // ready image +// 10100 bytes: +static MLX90640_params params; // calculated parameters (in heap, not stack!) for other functions void dumpIma(const fp_t im[MLX_PIXNO]){ for(int row = 0; row < MLX_H; ++row){ @@ -100,29 +103,29 @@ static void occacc(int8_t *arr, int l, const uint16_t *regstart){ } // get all parameters' values from `dataarray`, return FALSE if something failed -int get_parameters(const uint16_t dataarray[MLX_DMA_MAXLEN], MLX90640_params *params){ +MLX90640_params *get_parameters(const uint16_t dataarray[MLX_DMA_MAXLEN]){ #define CREG_VAL(reg) dataarray[CREG_IDX(reg)] int8_t i8; int16_t i16; uint16_t *pu16; uint16_t val = CREG_VAL(REG_VDD); i8 = (int8_t) (val >> 8); - params->kVdd = i8 * 32; // keep sign - if(params->kVdd == 0) return FALSE; + params.kVdd = i8 * 32; // keep sign + if(params.kVdd == 0){USND("kvdd=0"); return NULL;} i16 = val & 0xFF; - params->vdd25 = ((i16 - 0x100) * 32) - (1<<13); + params.vdd25 = ((i16 - 0x100) * 32) - (1<<13); val = CREG_VAL(REG_KVTPTAT); i16 = (val & 0xFC00) >> 10; if(i16 > 0x1F) i16 -= 0x40; - params->KvPTAT = (fp_t)i16 / (1<<12); + params.KvPTAT = (fp_t)i16 / (1<<12); i16 = (val & 0x03FF); if(i16 > 0x1FF) i16 -= 0x400; - params->KtPTAT = (fp_t)i16 / 8.; - params->vPTAT25 = (int16_t) CREG_VAL(REG_PTAT); + params.KtPTAT = (fp_t)i16 / 8.; + params.vPTAT25 = (int16_t) CREG_VAL(REG_PTAT); val = CREG_VAL(REG_APTATOCCS) >> 12; - params->alphaPTAT = val / 4. + 8.; - params->gainEE = (int16_t)CREG_VAL(REG_GAIN); - if(params->gainEE == 0) return FALSE; + params.alphaPTAT = val / 4. + 8.; + params.gainEE = (int16_t)CREG_VAL(REG_GAIN); + if(params.gainEE == 0){USND("gainee=0"); return NULL;} int8_t occRow[MLX_H]; int8_t occColumn[MLX_W]; occacc(occRow, MLX_H, &CREG_VAL(REG_OCCROW14)); @@ -150,11 +153,11 @@ int get_parameters(const uint16_t dataarray[MLX_DMA_MAXLEN], MLX90640_params *pa // so index of ktaavg is 2*(row&1)+(col&1) val = CREG_VAL(REG_KTAVSCALE); uint8_t scale1 = ((val & 0xFF)>>4) + 8, scale2 = (val&0xF); - if(scale1 == 0 || scale2 == 0) return FALSE; + if(scale1 == 0 || scale2 == 0){USND("scale1/2=0"); return NULL;} fp_t mul = (fp_t)(1<alpha; + fp_t *a = params.alpha; uint32_t diva32 = 1 << (val >> 12); fp_t diva = (fp_t)(diva32); diva *= (fp_t)(1<<30); // alpha_scale @@ -162,8 +165,8 @@ int get_parameters(const uint16_t dataarray[MLX_DMA_MAXLEN], MLX90640_params *pa accColumnScale = 1<<((val & 0x00f0)>>4), accRemScale = 1<<(val & 0x0f); pu16 = (uint16_t*)&CREG_VAL(REG_OFFAK1); - fp_t *kta = params->kta, *offset = params->offset; - uint8_t *ol = params->outliers; + fp_t *kta = params.kta, *offset = params.offset; + uint8_t *ol = params.outliers; for(int row = 0; row < MLX_H; ++row){ int idx = (row&1)<<1; for(int col = 0; col < MLX_W; ++col){ @@ -197,59 +200,59 @@ int get_parameters(const uint16_t dataarray[MLX_DMA_MAXLEN], MLX90640_params *pa ktaavg[2] = (int8_t)i16; // odd col, even row i16 = val & 0x0F; if(i16 > 0x07) i16 -= 0x10; ktaavg[3] = (int8_t)i16; // even col, even row - for(int i = 0; i < 4; ++i) params->kv[i] = ktaavg[i] / div; + for(int i = 0; i < 4; ++i) params.kv[i] = ktaavg[i] / div; val = CREG_VAL(REG_CPOFF); - params->cpOffset[0] = (val & 0x03ff); - if(params->cpOffset[0] > 0x1ff) params->cpOffset[0] -= 0x400; - params->cpOffset[1] = val >> 10; - if(params->cpOffset[1] > 0x1f) params->cpOffset[1] -= 0x40; - params->cpOffset[1] += params->cpOffset[0]; + params.cpOffset[0] = (val & 0x03ff); + if(params.cpOffset[0] > 0x1ff) params.cpOffset[0] -= 0x400; + params.cpOffset[1] = val >> 10; + if(params.cpOffset[1] > 0x1f) params.cpOffset[1] -= 0x40; + params.cpOffset[1] += params.cpOffset[0]; val = ((CREG_VAL(REG_KTAVSCALE) & 0xF0) >> 4) + 8; i8 = (int8_t)(CREG_VAL(REG_KVTACP) & 0xFF); - params->cpKta = (fp_t)i8 / (1<> 8; i16 = CREG_VAL(REG_KVTACP) >> 8; if(i16 > 0x7F) i16 -= 0x100; - params->cpKv = (fp_t)i16 / (1< 0x7F) i16 -= 0x100; - params->tgc = (fp_t)i16; - params->tgc /= 32.; + params.tgc = (fp_t)i16; + params.tgc /= 32.; val = (CREG_VAL(REG_SCALEACC)>>12); // alpha_scale_CP i16 = CREG_VAL(REG_ALPHA)>>10; // cp_P1_P0_ratio if(i16 > 0x1F) i16 -= 0x40; div = (fp_t)(1<cpAlpha[0] = (fp_t)(CREG_VAL(REG_ALPHA) & 0x03FF) / div; + params.cpAlpha[0] = (fp_t)(CREG_VAL(REG_ALPHA) & 0x03FF) / div; div = (fp_t)(1<<7); - params->cpAlpha[1] = params->cpAlpha[0] * (1. + (fp_t)i16/div); + params.cpAlpha[1] = params.cpAlpha[0] * (1. + (fp_t)i16/div); i8 = (int8_t)(CREG_VAL(REG_KSTATGC) >> 8); - params->KsTa = (fp_t)i8/(1<<13); + params.KsTa = (fp_t)i8/(1<<13); div = 1<<((CREG_VAL(REG_CT34) & 0x0F) + 8); // kstoscale val = CREG_VAL(REG_KSTO12); i8 = (int8_t)(val & 0xFF); - params->KsTo[0] = i8 / div; + params.KsTo[0] = i8 / div; i8 = (int8_t)(val >> 8); - params->KsTo[1] = i8 / div; + params.KsTo[1] = i8 / div; val = CREG_VAL(REG_KSTO34); i8 = (int8_t)(val & 0xFF); - params->KsTo[2] = i8 / div; + params.KsTo[2] = i8 / div; i8 = (int8_t)(val >> 8); - params->KsTo[3] = i8 / div; + params.KsTo[3] = i8 / div; // CT1 = -40, CT2 = 0 -> start from zero index, so CT[0] is CT2, CT[1] is CT3, CT[2] is CT4 - params->CT[0] = 0.; // 0degr - between ranges 1 and 2 + params.CT[0] = 0.; // 0degr - between ranges 1 and 2 val = CREG_VAL(REG_CT34); mul = ((val & 0x3000)>>12)*10.; // step - params->CT[1] = ((val & 0xF0)>>4)*mul; // CT3 - between ranges 2 and 3 - params->CT[2] = ((val & 0x0F00) >> 8)*mul + params->CT[1]; // CT4 - between ranges 3 and 4 + params.CT[1] = ((val & 0xF0)>>4)*mul; // CT3 - between ranges 2 and 3 + params.CT[2] = ((val & 0x0F00) >> 8)*mul + params.CT[1]; // CT4 - between ranges 3 and 4 // alphacorr for each range: 11.1.11 - params->alphacorr[0] = 1./(1. + params->KsTo[0] * 40.); - params->alphacorr[1] = 1.; - params->alphacorr[2] = (1. + params->KsTo[1] * params->CT[1]); - params->alphacorr[3] = (1. + params->KsTo[2] * (params->CT[2] - params->CT[1])) * params->alphacorr[2]; - params->resolEE = (uint8_t)((CREG_VAL(REG_KTAVSCALE) & 0x3000) >> 12); + params.alphacorr[0] = 1./(1. + params.KsTo[0] * 40.); + params.alphacorr[1] = 1.; + params.alphacorr[2] = (1. + params.KsTo[1] * params.CT[1]); + params.alphacorr[3] = (1. + params.KsTo[2] * (params.CT[2] - params.CT[1])) * params.alphacorr[2]; + params.resolEE = (uint8_t)((CREG_VAL(REG_KTAVSCALE) & 0x3000) >> 12); // Don't forget to check 'outlier' flags for wide purpose - return TRUE; + return ¶ms; #undef CREG_VAL } @@ -261,37 +264,37 @@ int get_parameters(const uint16_t dataarray[MLX_DMA_MAXLEN], MLX90640_params *pa * @param subpageno * @return */ -fp_t *process_image(const MLX90640_params *params, const int16_t subpage1[REG_IMAGEDATA_LEN]){ +fp_t *process_image(const int16_t subpage1[REG_IMAGEDATA_LEN]){ #define IMD_VAL(reg) subpage1[IMD_IDX(reg)] // 11.2.2.1. Resolution restore - //fp_t resol_corr = (fp_t)(1<resolEE) / (1<resolEE) / (1<<2); // ONLY DEFAULT! + //fp_t resol_corr = (fp_t)(1< - + EnvironmentId diff --git a/F3:F303/MLX90640multi/mlx90640.h b/F3:F303/MLX90640multi/mlx90640.h index 1db012e..da60814 100644 --- a/F3:F303/MLX90640multi/mlx90640.h +++ b/F3:F303/MLX90640multi/mlx90640.h @@ -59,7 +59,7 @@ typedef struct{ } MLX90640_params; int ch_resolution(uint8_t newresol); -int get_parameters(const uint16_t dataarray[MLX_DMA_MAXLEN], MLX90640_params *params); -fp_t *process_image(const MLX90640_params *params, const int16_t subpage1[REG_IMAGEDATA_LEN]); +MLX90640_params *get_parameters(const uint16_t dataarray[MLX_DMA_MAXLEN]); +fp_t *process_image(const int16_t subpage1[REG_IMAGEDATA_LEN]); void dumpIma(const fp_t im[MLX_PIXNO]); void drawIma(const fp_t im[MLX_PIXNO]); diff --git a/F3:F303/MLX90640multi/mlxproc.c b/F3:F303/MLX90640multi/mlxproc.c index 4986863..8c0719a 100644 --- a/F3:F303/MLX90640multi/mlxproc.c +++ b/F3:F303/MLX90640multi/mlxproc.c @@ -21,8 +21,20 @@ #include "i2c.h" #include "mlxproc.h" #include "mlx90640_regs.h" + +//#define DEBUGPROC + +#ifdef DEBUGPROC #include "usb_dev.h" #include "strfunc.h" +#define D(x) U(x) +#define DN(x) USND(x) +#define DB(x) USB_putbute(x) +#else +#define D(x) +#define DN(x) +#define DB(x) +#endif extern volatile uint32_t Tms; @@ -32,11 +44,19 @@ static int errctr = 0; // errors counter - cleared by mlx_continue static uint32_t Tlastimage[N_SESORS] = {0}; // subpages and configs of all sensors +// 8320 bytes: static int16_t imdata[N_SESORS][REG_IMAGEDATA_LEN]; +// 8340 bytes: static uint16_t confdata[N_SESORS][MLX_DMA_MAXLEN]; static uint8_t sens_addresses[N_SESORS] = {0x10<<1, 0x11<<1, 0x12<<1, 0x13<<1, 0x14<<1}; // addresses of all sensors (if 0 - omit this one) static uint8_t sensaddr[N_SESORS]; +// get compile-time size: (gcc shows it in error message) +//char (*__kaboom)[sizeof( confdata )] = 1; + +// return `sensaddr` +uint8_t *mlx_activeids(){return sensaddr;} + static int sensno = -1; // get current state @@ -54,12 +74,14 @@ void mlx_pause(){ MLX_oldstate = MLX_state; MLX_state = MLX_RELAX; } +// TODO: add here power management void mlx_stop(){ MLX_oldstate = MLX_NOTINIT; MLX_state = MLX_RELAX; } // continue processing +// TODO: add here power management void mlx_continue(){ errctr = 0; switch(MLX_oldstate){ @@ -70,6 +92,7 @@ void mlx_continue(){ //case MLX_NOTINIT: //case MLX_WAITPARAMS: default: + i2c_setup(i2c_curspeed); // restart I2C (what if there was errors?) memcpy(sensaddr, sens_addresses, sizeof(sens_addresses)); MLX_state = MLX_NOTINIT; sensno = -1; @@ -85,7 +108,7 @@ static int nextsensno(int s){ int next = s + 1; for(; next < N_SESORS; ++next) if(sensaddr[next]) break; if(next == N_SESORS) return nextsensno(-1); // roll to start - U(i2str(next)); USND(" - new sensor number"); + D(i2str(next)); DB('('); D(i2str(s)); DB(')'); DN(" - new sensor number"); return next; } @@ -115,20 +138,20 @@ void mlx_process(){ switch(MLX_state){ case MLX_NOTINIT: // start reading parameters if(i2c_read_reg16(sensaddr[sensno], REG_CALIDATA, MLX_DMA_MAXLEN, 1)){ - U(i2str(sensno)); USND(" wait conf"); + D(i2str(sensno)); DN(" wait conf"); errctr = 0; MLX_state = MLX_WAITPARAMS; }else ++errctr; break; case MLX_WAITPARAMS: // check DMA ends and calculate parameters - if(i2c_dma_haderr()){ MLX_state = MLX_NOTINIT; USND("DMA err");} + if(i2c_dma_haderr()){ MLX_state = MLX_NOTINIT; DN("DMA err");} else{ uint16_t len, *buf = i2c_dma_getbuf(&len); - if(buf) USND("READ"); - else break; + if(!buf) break; + DN("READ"); if(len != MLX_DMA_MAXLEN){ MLX_state = MLX_NOTINIT; break; } memcpy(confdata[sensno], buf, MLX_DMA_MAXLEN * sizeof(uint16_t)); - U(i2str(sensno)); USND(" got conf"); + D(i2str(sensno)); DN(" got conf"); int next = nextsensno(sensno); errctr = 0; if(next <= sensno) MLX_state = MLX_WAITSUBPAGE; // all configuration read @@ -143,14 +166,20 @@ void mlx_process(){ if(subpage == (*got & REG_STATUS_SPNO)){ errctr = 0; if(subpage == 0){ // omit zero subpage for each sensor + DN("omit 0 -> next sens"); int next = nextsensno(sensno); - if(next <= sensno) subpage = 1; // all scanned - now wait for page 1 + if(next <= sensno){ // all scanned - now wait for page 1 + subpage = 1; + DN("Wait for 1"); + } + sensno = next; break; } + D(i2str(sensno)); DN(" - ask for image"); if(i2c_read_reg16(sensaddr[sensno], REG_IMAGEDATA, REG_IMAGEDATA_LEN, 1)){ errctr = 0; MLX_state = MLX_READSUBPAGE; - // U("spstart"); USB_putbyte('0'+subpage); USB_putbyte('='); USND(u2str(Tms - Tlast)); + // D("spstart"); DB('0'+subpage); DB('='); DN(u2str(Tms - Tlast)); }else ++errctr; } }else ++errctr; @@ -161,17 +190,21 @@ void mlx_process(){ else{ uint16_t len, *buf = i2c_dma_getbuf(&len); if(buf){ - // U("spread="); USND(u2str(Tms - Tlast)); + // D("spread="); DN(u2str(Tms - Tlast)); if(len != REG_IMAGEDATA_LEN){ ++errctr; }else{ // fine! we could check next sensor errctr = 0; memcpy(imdata[sensno], buf, REG_IMAGEDATA_LEN * sizeof(int16_t)); - // U("spgot="); USND(u2str(Tms - Tlast)); + // D("spgot="); DN(u2str(Tms - Tlast)); Tlastimage[sensno] = Tms; - // U("imgot="); USND(u2str(Tms - Tlast)); Tlast = Tms; + // D("imgot="); DN(u2str(Tms - Tlast)); Tlast = Tms; int next = nextsensno(sensno); - if(next <= sensno) subpage = 0; // roll to start - omit page 0 for all + if(next <= sensno){ + subpage = 0; // roll to start - omit page 0 for all + DN("All got -> start from 0"); + } + sensno = next; } MLX_state = MLX_WAITSUBPAGE; } @@ -180,7 +213,7 @@ void mlx_process(){ default: return; } - if(MLX_state != MLX_RELAX && Tms - Tlastimage[sensno] > MLX_I2CERR_TMOUT){ i2c_setup(i2c_curspeed); Tlastimage[sensno] = Tms; } + //if(MLX_state != MLX_RELAX && Tms - Tlastimage[sensno] > MLX_I2CERR_TMOUT){ i2c_setup(i2c_curspeed); Tlastimage[sensno] = Tms; } if(errctr > MLX_MAX_ERRORS){ errctr = 0; sensaddr[sensno] = 0; // throw out this value @@ -189,19 +222,18 @@ void mlx_process(){ } // recalculate parameters -int mlx_getparams(int n, MLX90640_params *pars){ - if(!pars) return 0; - if(!get_parameters(confdata[n], pars)) return 0; - return 1; +MLX90640_params *mlx_getparams(int n){ + MLX90640_params *p = get_parameters(confdata[n]); + return p; } uint32_t mlx_lastimT(int n){ return Tlastimage[n]; } fp_t *mlx_getimage(int n){ if(n < 0 || n >= N_SESORS || !sensaddr[n]) return NULL; - MLX90640_params p; - if(!get_parameters(confdata[n], &p)) return NULL; - fp_t *ready_image = process_image(&p, imdata[n]); + MLX90640_params *p = get_parameters(confdata[n]); + if(!p) return NULL; + fp_t *ready_image = process_image(imdata[n]); if(!ready_image) return NULL; return ready_image; } @@ -212,7 +244,7 @@ int mlx_sethwaddr(uint8_t MLX_address, uint8_t addr){ if(addr > 0x7f) return 0; uint16_t data[2], *ptr; if(!(ptr = i2c_read_reg16(MLX_address, REG_MLXADDR, 1, 0))) return 0; - //U("Old address: "); USND(uhex2str(*ptr)); + //D("Old address: "); DN(uhex2str(*ptr)); data[0] = REG_MLXADDR; data[1] = 0; uint16_t oldreg = *ptr; if(!i2c_write(MLX_address, data, 2)) return 0; // clear address @@ -227,11 +259,11 @@ int mlx_sethwaddr(uint8_t MLX_address, uint8_t addr){ } data[0] = REG_MLXADDR; // i2c_write swaps bytes, so we need init data again data[1] = (oldreg & ~REG_MLXADDR_MASK) | addr; - //U("Write address: "); U(uhex2str(data[0])); U(", "); USND(uhex2str(data[1])); + //D("Write address: "); D(uhex2str(data[0])); D(", "); DN(uhex2str(data[1])); if(!i2c_write(MLX_address, data, 2)) return 0; while(Tms - Told < 10); if(!(ptr = i2c_read_reg16(MLX_address, REG_MLXADDR, 1, 0))) return 0; - //U("Got address: "); USND(uhex2str(*ptr)); + //D("Got address: "); DN(uhex2str(*ptr)); if((*ptr & REG_MLXADDR_MASK) != addr) return 0; return 1; } diff --git a/F3:F303/MLX90640multi/mlxproc.h b/F3:F303/MLX90640multi/mlxproc.h index a93032d..c67e759 100644 --- a/F3:F303/MLX90640multi/mlxproc.h +++ b/F3:F303/MLX90640multi/mlxproc.h @@ -41,11 +41,12 @@ typedef enum{ int mlx_setaddr(int n, uint8_t addr); mlx_state_t mlx_state(); int mlx_nactive(); +uint8_t *mlx_activeids(); void mlx_pause(); void mlx_stop(); void mlx_continue(); void mlx_process(); -int mlx_getparams(int sensno, MLX90640_params *pars); +MLX90640_params *mlx_getparams(int sensno); fp_t *mlx_getimage(int sensno); int mlx_sethwaddr(uint8_t MLX_address, uint8_t addr); uint32_t mlx_lastimT(int sensno); diff --git a/F3:F303/MLX90640multi/proto.c b/F3:F303/MLX90640multi/proto.c index 0fd60db..ce42124 100644 --- a/F3:F303/MLX90640multi/proto.c +++ b/F3:F303/MLX90640multi/proto.c @@ -32,17 +32,24 @@ static uint8_t I2Caddress = 0x33 << 1; extern volatile uint32_t Tms; uint8_t cartoon = 0; // "cartoon" mode: refresh image each time we get new +// common names for frequent keys +const char *Timage = "TIMAGE="; +const char *Sensno = "SENSNO="; + static const char *OK = "OK\n", *ERR = "ERR\n"; const char *helpstring = "https://github.com/eddyem/stm32samples/tree/master/F3:F303/mlx90640 build#" BUILD_NUMBER " @ " BUILD_DATE "\n" " management of single IR bolometer MLX90640\n" "aa - change I2C address to a (a should be non-shifted value!!!)\n" "c - continue MLX\n" - "d - draw image in ASCII\n" + "dn - draw nth image in ASCII\n" + "gn - get nth image 'as is' - float array of 768x4 bytes\n" "i0..4 - setup I2C with speed 10k, 100k, 400k, 1M or 2M (experimental!)\n" + "l - list active sensors IDs\n" + "tn - show temperature map of nth image\n" "p - pause MLX\n" "s - stop MLX (and start from zero @ 'c'\n" - "t - show temperature map\n" + "tn - show nth image aquisition time\n" "C - \"cartoon\" mode on/off (show each new image)\n" "Dn - dump MLX parameters for sensor number n\n" "G - get MLX state\n" @@ -90,16 +97,23 @@ TRUE_INLINE const char *chhwaddr(const char *buf){ return OK; } +// read sensor's number from `buf`; return -1 if error +static int getsensnum(const char *buf){ + if(!buf || !*buf) return -1; + uint32_t num; + const char *nxt = getnum(buf, &num); + if(!nxt || nxt == buf || num >= N_SESORS) return -1; + return (int) num; +} + TRUE_INLINE const char *chaddr(const char *buf){ - uint32_t addr, num; + uint32_t addr; const char *nxt = getnum(buf, &addr); if(nxt && nxt != buf){ if(addr > 0x7f) return ERR; I2Caddress = (uint8_t) addr << 1; - buf = getnum(nxt, &num); - if(buf && nxt != buf && num < N_SESORS){ - mlx_setaddr(num, addr); - } + int n = getsensnum(nxt); + if(n > -1) mlx_setaddr(n, addr); }else addr = I2Caddress >> 1; U("I2CADDR="); USND(uhex2str(addr)); return NULL; @@ -156,21 +170,20 @@ static void dumpfarr(float *arr){ } // dump MLX parameters TRUE_INLINE void dumpparams(const char *buf){ - uint32_t N = 0; - const char *nxt = getnum(buf, &N); - U(u2str(N)); USND("sn"); - if(!nxt || buf == nxt || N > N_SESORS){ U(ERR); return; } - MLX90640_params params; - if(!mlx_getparams(N, ¶ms)){ U(ERR); return; } - U("\nkVdd="); printi(params.kVdd); - U("\nvdd25="); printi(params.vdd25); - U("\nKvPTAT="); printfl(params.KvPTAT, 4); - U("\nKtPTAT="); printfl(params.KtPTAT, 4); - U("\nvPTAT25="); printi(params.vPTAT25); - U("\nalphaPTAT="); printfl(params.alphaPTAT, 2); - U("\ngainEE="); printi(params.gainEE); + int N = getsensnum(buf); + if(N < 0){ U(ERR); return; } + MLX90640_params *params = mlx_getparams(N); + if(!params){ U(ERR); return; } + U(Sensno); USND(i2str(N)); + U("\nkVdd="); printi(params->kVdd); + U("\nvdd25="); printi(params->vdd25); + U("\nKvPTAT="); printfl(params->KvPTAT, 4); + U("\nKtPTAT="); printfl(params->KtPTAT, 4); + U("\nvPTAT25="); printi(params->vPTAT25); + U("\nalphaPTAT="); printfl(params->alphaPTAT, 2); + U("\ngainEE="); printi(params->gainEE); U("\nPixel offset parameters:\n"); - float *offset = params.offset; + float *offset = params->offset; for(int row = 0; row < 24; ++row){ for(int col = 0; col < 32; ++col){ printfl(*offset++, 2); USB_putbyte(' '); @@ -178,28 +191,28 @@ TRUE_INLINE void dumpparams(const char *buf){ newline(); } U("K_talpha:\n"); - dumpfarr(params.kta); + dumpfarr(params->kta); U("Kv: "); for(int i = 0; i < 4; ++i){ - printfl(params.kv[i], 2); USB_putbyte(' '); + printfl(params->kv[i], 2); USB_putbyte(' '); } U("\ncpOffset="); - printi(params.cpOffset[0]); U(", "); printi(params.cpOffset[1]); - U("\ncpKta="); printfl(params.cpKta, 2); - U("\ncpKv="); printfl(params.cpKv, 2); - U("\ntgc="); printfl(params.tgc, 2); - U("\ncpALpha="); printfl(params.cpAlpha[0], 2); - U(", "); printfl(params.cpAlpha[1], 2); - U("\nKsTa="); printfl(params.KsTa, 2); + printi(params->cpOffset[0]); U(", "); printi(params->cpOffset[1]); + U("\ncpKta="); printfl(params->cpKta, 2); + U("\ncpKv="); printfl(params->cpKv, 2); + U("\ntgc="); printfl(params->tgc, 2); + U("\ncpALpha="); printfl(params->cpAlpha[0], 2); + U(", "); printfl(params->cpAlpha[1], 2); + U("\nKsTa="); printfl(params->KsTa, 2); U("\nAlpha:\n"); - dumpfarr(params.alpha); - U("\nCT3="); printfl(params.CT[1], 2); - U("\nCT4="); printfl(params.CT[2], 2); + dumpfarr(params->alpha); + U("\nCT3="); printfl(params->CT[1], 2); + U("\nCT4="); printfl(params->CT[2], 2); for(int i = 0; i < 4; ++i){ U("\nKsTo"); USB_putbyte('0'+i); USB_putbyte('='); - printfl(params.KsTo[i], 2); + printfl(params->KsTo[i], 2); U("\nalphacorr"); USB_putbyte('0'+i); USB_putbyte('='); - printfl(params.alphacorr[i], 2); + printfl(params->alphacorr[i], 2); } newline(); } @@ -217,34 +230,81 @@ TRUE_INLINE void getst(){ USND(states[s]); } -// `draw`==1 - draw, ==0 - show T map +// `draw`==1 - draw, ==0 - show T map, 2 - send raw float array with prefix 'SENSNO=x\nTimage=y\n' and postfix "ENDIMAGE\n" static const char *drawimg(const char *buf, int draw){ - uint32_t sensno; - const char *nxt = getnum(buf, &sensno); - if(nxt && nxt != buf && sensno < N_SESORS){ + int sensno = getsensnum(buf); + if(sensno > -1){ uint32_t T = mlx_lastimT(sensno); fp_t *img = mlx_getimage(sensno); if(img){ - U("Timage="); USND(u2str(T)); - if(draw) drawIma(img); - else dumpIma(img); + U(Sensno); USND(u2str(sensno)); + U(Timage); USND(u2str(T)); + switch(draw){ + case 0: + dumpIma(img); + break; + case 1: + drawIma(img); + break; + case 2: + { + uint8_t *d = (uint8_t*)img; + uint32_t _2send = MLX_PIXNO * sizeof(float); + // send by portions of 256 bytes (as image is larger than ringbuffer) + while(_2send){ + uint32_t portion = (_2send > 256) ? 256 : _2send; + USB_send(d, portion); + _2send -= portion; + d += portion; + } + } + USND("ENDIMAGE"); + } return NULL; } } return ERR; } +TRUE_INLINE void listactive(){ + int N = mlx_nactive(); + if(!N){ USND("No active sensors found!"); return; } + uint8_t *ids = mlx_activeids(); + U("Found "); USB_putbyte('0'+N); USND(" active sensors:"); + for(int i = 0; i < N_SESORS; ++i) + if(ids[i]){ + U("SENSID"); U(u2str(i)); USB_putbyte('='); + U(uhex2str(ids[i] >> 1)); + newline(); + } +} + +static void getimt(const char *buf){ + int sensno = getsensnum(buf); + if(sensno > -1){ + U(Timage); USND(u2str(mlx_lastimT(sensno))); + }else U(ERR); +} + const char *parse_cmd(char *buf){ if(!buf || !*buf) return NULL; if(buf[1]){ switch(*buf){ // "long" commands case 'a': return chhwaddr(buf + 1); + case 'd': + return drawimg(buf+1, 1); + case 'g': + return drawimg(buf+1, 2); case 'i': return setupI2C(buf + 1); + case 'm': + return drawimg(buf+1, 0); + case 't': + getimt(buf + 1); return NULL; case 'D': dumpparams(buf + 1); - return; + return NULL; break; case 'I': buf = omit_spaces(buf + 1); @@ -270,18 +330,15 @@ const char *parse_cmd(char *buf){ case 'c': mlx_continue(); return OK; break; - case 'd': - return drawimg(buf+1, 1); - break; case 'i': return setupI2C(NULL); // current settings + case 'l': + listactive(); + break; case 'p': mlx_pause(); return OK; break; case 's': mlx_stop(); return OK; - case 't': - return drawimg(buf+1, 0); - break; case 'C': cartoon = !cartoon; return OK; case 'G': diff --git a/F3:F303/MLX90640multi/proto.h b/F3:F303/MLX90640multi/proto.h index 1d48512..f3afdb7 100644 --- a/F3:F303/MLX90640multi/proto.h +++ b/F3:F303/MLX90640multi/proto.h @@ -18,5 +18,7 @@ #pragma once +extern const char *Timage, *Sensno; + extern uint8_t cartoon; char *parse_cmd(char *buf); diff --git a/F3:F303/MLX90640multi/usb_dev.h b/F3:F303/MLX90640multi/usb_dev.h index f787b12..b8a0808 100644 --- a/F3:F303/MLX90640multi/usb_dev.h +++ b/F3:F303/MLX90640multi/usb_dev.h @@ -44,7 +44,7 @@ void linecoding_handler(usb_LineCoding *lc); // sizes of ringbuffers for outgoing and incoming data #define RBOUTSZ (1024) -#define RBINSZ (1024) +#define RBINSZ (128) #define newline() USB_putbyte('\n') #define USND(s) do{USB_sendstr(s); USB_putbyte('\n');}while(0) diff --git a/F3:F303/MLX90640multi/version.inc b/F3:F303/MLX90640multi/version.inc index b668fbf..d50b410 100644 --- a/F3:F303/MLX90640multi/version.inc +++ b/F3:F303/MLX90640multi/version.inc @@ -1,2 +1,2 @@ -#define BUILD_NUMBER "30" -#define BUILD_DATE "2025-09-23" +#define BUILD_NUMBER "65" +#define BUILD_DATE "2025-09-24"