From 863b9fddb7ad4ef0e14e8f937f247c044884e26e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=AE=D1=80=D0=B8=D0=B9=20=D0=9C=D0=B0=D0=BA=D0=B0=D1=80?= =?UTF-8?q?=D0=BE=D0=B2?= Date: Tue, 4 Oct 2022 19:29:14 +0300 Subject: [PATCH] ports/esp32/esp32_can: Add CAN bus support. CAN bus for esp32 controller. --- docs/esp32/img/twai_blockdiag.png | Bin 0 -> 10157 bytes docs/esp32/quickref.rst | 25 + docs/library/esp32.rst | 262 ++++++ docs/library/img/can_mode_loopback.png | Bin 0 -> 3669 bytes docs/library/img/can_mode_normal.png | Bin 0 -> 3513 bytes docs/library/img/can_mode_silent.png | Bin 0 -> 3632 bytes docs/library/img/can_mode_silent_loopback.png | Bin 0 -> 3646 bytes examples/esp32_can.py | 60 ++ ports/esp32/esp32_can.c | 764 ++++++++++++++++++ ports/esp32/esp32_can.h | 68 ++ ports/esp32/main/CMakeLists.txt | 1 + ports/esp32/modesp32.c | 3 + ports/esp32/modesp32.h | 1 + ports/esp32/mpconfigport.h | 1 + tests/esp32/can.py | 234 ++++++ tests/esp32/can.py.exp | 45 ++ 16 files changed, 1464 insertions(+) create mode 100644 docs/esp32/img/twai_blockdiag.png create mode 100755 docs/library/img/can_mode_loopback.png create mode 100755 docs/library/img/can_mode_normal.png create mode 100755 docs/library/img/can_mode_silent.png create mode 100755 docs/library/img/can_mode_silent_loopback.png create mode 100644 examples/esp32_can.py create mode 100644 ports/esp32/esp32_can.c create mode 100644 ports/esp32/esp32_can.h create mode 100644 tests/esp32/can.py create mode 100644 tests/esp32/can.py.exp diff --git a/docs/esp32/img/twai_blockdiag.png b/docs/esp32/img/twai_blockdiag.png new file mode 100644 index 0000000000000000000000000000000000000000..183352ea610de1aa53b149730db94cf068661609 GIT binary patch literal 10157 zcmd6N2T)U6yY^0Kp$PF%6{L6+DUo6U6a)c5jUpNdB@l`Vf}skbm(WBh>QRaUf`S1I z1`r5UAb`q2MB1UZAPPvw(Cc0CeBaDB_rG)hf9Br#?`6VdvS;nJ_IlS^?^E7=|LT?V zdwBNqKoGR&;)Qb<2!hig2OFVHWUtFlWz6VwWDf*ek z7IWpDO5681IPZ>1msdcb-vOT|65Xq8NX>?Eaz*E%&@HAdwgacg0mf!<6DJRVZ)oK{ z#0S0#kR%wmDhyWvKi=s<;Q2`A2s-$&zylKlUp6uj8wAl6+3mm&zo`FjHyzek;+Ht( zKY4)OR2V%R#>QBWs99UvI)#uYTYA^f%v`;mtvHB2ubuHp!u#|Q&G8{mnwRXmZoFSS z2q||5kB!GmJnVYs+5BZbCvHMuPnoPwv zTZZbZwNax1pAa1btA}B_i2VK zd?C5tz5iX<4MfhC>=!1NzUKFd);gEpG&XtsQ-^yVCHA_>xy|LAs;#cBri$lQ4fOZS z&TZE8s}`PuFX$I}O?{0Lokrb;IZ3JK1o16y)#V3;?AX~2*$uhPTZq0ZpaVsZs9F0On1#^0_}CFhY(cx$n|S-BbTS-agl zC@mM#DVa3riBaBcBVqf675g@ZqNV?2a#j6YU-vRiqVb^myN5#Fth5Wp<%B_7WCz>6 zehwYBJnxbB$*!7*K@3EtGIY-0!ZOZe7Y?}LdvEsB9JVX;XF4-C8$6|}>5zG*uoTgMtwBJZd0w{KLL@pTti-U&^j~5|{o0GtBnek!Wix zR&oineb|>Atj(Fm$)E7utlHnCNw25q#`~UObI=)QYX;juPg=$F93dH5)wC?K=V{>Q zU~1LFxlEZQNhv7>`oLI}Sl(4CE`CD5Y;@i0>}Fa$p)YukWu5C>v1amop9$|Z_yjw_*<*g(uc+g0n(YVCx80*)qRlFxFRyP%aO{^ zfT?KUq&f9!TYrq^DMHW{Hdy&Bs{by3wc?~Dv+U$YCwVf`lmd>zV+8%oojr5W!+ZE% ztq-8(2{%4(dqu18LG;dg^U5Es-YDjk!GgyR0tyFL_mXV^#!4CXa^DD z^4Hqb$lXiwJ-6Ir+l~9fp>NgT+IuXP zfXnDm^&i55{p5XeQNRqF9ec00$#~Z6zi5MJ}Ml{Lx z46-|t`uWIkdPe2RJ-?64!RS2fi~|c;^+@E`m6eqW%qJ%*fl9lE-ZyZ9&4lA4f8}VY zxQi&q&3a|%&3TB^$J@(`sRbVSxdN3aPcC`GdGDnq0V@~>nn3jH?<}+>O>A@#GVf<+ zXZ_St<9})yQz$WmZ;%pFy}Y0gmxrRC&tG3;JMmLXC$+`#mcg}G4wiR`c!K)x?2QPK z$C#A4A8niLkoHTC>s*oI2v2q1bJv8NuL)hWO!~u0w!%fhD|%Q*;$gQmjXV&i8j$*( z<{So*W*4uo`$gI!WFr4$YPrHa@I6KxPsiA7oyPfW)%;5PvJ)t&i5xw&juL%ksdC89 zi=A=)>$K~Mapt;Z`h>G((lspBo`BV8QiaQuTB$TfF+LrATV0aqXxV$1osAw)a(2ei zzP#_|lp=xp2&1&A;62Su9T;3X9gFIp&r#{J+EjI8`fWa*@HZ1OuYi2@w)8}c$kTvmB8I|P{=E+OxdQR&%LXq z@eVK?x^J^aYIFC)jn5Oz5}HKO+~=VHoPdacOV!%@%$^4ht#w7H8&u+JT7DGL5Mlpq zDC$%`gG0o(?yANM$(-aV8xxM=`)4J-Rb8Rmoqkl2*WbIZw~gcc2J*H>sv}${kN1Dn0Ml<*K}~ zzbw#3D)@6=$jErn^qj9CiWo;s3Vt*_{gYIHsO#krW(@h6gjot%i*hVGB=^0~j#yKp zKGN~YC|e#F*;%O#UhXmv-EJ}t`579zH72!N%VaVi-nw!QIzm-xnj5 zsR1E-L*6RF7s6<4mYPqGYDx|Co#!w@JE!iPXqNgENcH!Zk_ejCMk<{Sl6}$?tHQ?w zA87=dO8oJlOxsu7?>9uvBrk03KJG8$fnh`bs^S9esqWS!yRSK4#EXUW{sapv@38Z9 z>?&GbNm;oz{j#ib$BAmA$KTW*jQJzkqw-MdT>FiE(zL>rHw>YWdb4F=cQ0}F@u6}` z0nzsSlaMpTH??i-lTnc!2ZZ{y3#P}%$AeDn$G12Z7iY?KP$>DSeTFIf&acs;Yp zp!5nX=U5S{p)#M%H$%c*!#~oN?|#HI0+m;UiR2#gFa$feC|6INyih$s!DTb7y_zv zo!DJzhQ^{_lvKlp|}b>+9nb`N2k zBDnEJM6uRvu{T zoHA^9?F4W5i#uI*3UyE+8(gI@>k2RD_A_KA&H8P3B~SQ^dt-h{4t+PZBtw0UpFXFD z*vqc_2S_kIv~FCFr-bqOtoE)CA3l^#OiW~a99x_Ky@DqWmzJ{@e)>Tgvg2a6vpcV} zq(GoPPWaZ=L4lH$AlqbzeLchjJf5N*C^t8h?faPZg!6c z9UXbov=rxX@8i2G`5Ow6bNYW^K1JF;&2bLUOl|oBz%X8w5*(7`@C4PI51#wVNLa5kAa1%J1*kDZ?%wF*vIFwMi(IZ+i?i-}y7!)-ObPh%#M`a z!jC7In33%{-(A{|(aVp&buZr*aZ91#I`Zyw;EoBY^f+a(GG7(gLf zitmT`Alx*LfUgPN-O;%uzgvbZxsaBb9lqM4KC+c(RpcYtxaca;c)ToV_T7&jQ?w^! z_iT>GP2hpBo-)K23%z4AC##}gEATZOjLBTj*jdU6t*ETr@Wp)E^_<++udP`hPYrc7 z{7Rrs%fjlj+pGMIGWwKNgcIyEQC(@s2n+*nADyaF-OdeRbhL0!awN7kv6Vtxj(H zx0DzdNHj7}7`cD>@&zQ-gDg}RPJczA{E1U6>8{+@H59ktb0(`}nEZBJdu_ujG^J~x zM$GRo$*=-ie#*5?U8K-8p_XD)Ra2469UmVx&xp}+H*`tLDWJkMYyc;_C{M+FV%Kdm zC1uHOq2;46lut~pW~Np4%zvmse+IW;Qh*xYw(E*32U=!Z;z)@8=+!d~Us2 zWGD4A&a>>BYbTfWiGc*nN2~zg4qG3Ww`Bn>{6%s};qa((-rAN{ zkoS3Icw{1-l1|5GI;CejV}uMaNe4waOOUt65S3PK_BXgkwixAPXpc1<-Kl`o_9j0p7L!`#5R^lCI2Gvb64qPRmOO$sI zMu#oXmR0g!Zi@y?f1$WH*Uv{(?Vr&F5}t;j3(z)$QroJq2%qCA&$`VgX-GY_h%xK$ z$rMIs+vSIjgKs1a%hxlinhz8pYG;StcE-V07lW;F#Rddd$q-$gTw90RK6B(HP^dIC z;b>)|ZO=*}^GlX(?2Z!k1))EoxDDq)?j;*wN1hYIJ1d)4{Cg&K1wsX@ zzS%;M2gJ^Zu9SIdxv!8HWL8My7(3&G$WcGoO!y51h>~mI~a85&JZrIe?q-hSyKR7Kq@~5Lh%iaa*E1Z5orrqJQPJkewqDY z7HDTgEmXMA1_fhq6ZBL@MIp8G0xHmqWm$pwy)G*6Y_p&@%+42hIK5BIcIP@=KZ+eC z3+dh4z++>lDm$GUOJ09z3Dkx0Yg*QI`^ZcMPhz zMmgqSK1D(0qa4}PXpMt3w3h_4GZw*y_PA!WPTacqc<`?MQvsSS#1QY3w1zQCRHUaC z`h^)p*qUlGSCHX|rev(79pnA%k3D)mS5|#TPWc02o`}&bQ+yY=I+v`oyWYCn6SH~h z14LhBx4W9NR?iKccgE{1WX?Wm`u4$g=V$Wn(nBCAbohRa0=C+cF^|km%cR8{17fcd zzW)6AbGqr~&+Dh3KVWJZ>xAxX&F6IfC?COdK)b+RdvU89XHUgze5`l13QR}p4Gs>b z8Rv8^x~OR?#vF8YjF5{t_k6^`!>KbWS5kz4vn>>s)wevPW-J{ zJuQ>$@jYSF>xp^@`?&T99XO<+3)WTaf(iG|!e~RQnrqY}OiWLK?QTUr!8MLMP8F~; zuxDN?==iwVL-YgS@%hf7@~qyxX=kgrD|Cz#1QgiGgw_GIUPRENHAtmqkMXa^IC z#O17}n|H1W8Q^+|FKGFoNqpm2=mS8V+nu}<9r_M|gfAE4sEahs-+%eA802#nY}p-4 znaD@L<{$#h+S|B@YA$L5^mE41`(7jx(6fWB%s2VK+Q=W%NN4rLBD$@utvaCRR?8%# zuNvtbx>w5;RAO}@7So4R9+`2Jd&AZ$QO)Jg>QSHrqs~+yefHgelT8y3HF*Ov|2!ij zw6eT>t(${E5G6Hd!-FOpIU(bHz%S)w^b5u%U~W?Zd`tlPD}EyZspmv(29M3smaWXc z#DFP7tiotXKGe|(T$%Ao$?gFFOY0_!^WAa^g-DI89iZ|CsaL7%K@__i<-uH4AYTUI z1khdFH}eM7+$)yVKXJ=at*L0{v{C0YkSz9MBDm@PJ>dj5DQE%lYd8FdG#HonDnXX7 zp`!(^>UI3=XZ#wq-{j?iDODIJNFiV~?JUfO$!5RLQEI`0mUKHH*$n~{)y;=ax&?OOpsd$hJi60t&`caK zj2o){g{abKW;`;lXe+Vn5mraW^3N}DkkVP*8C%~={Oaa|keLggk4V4UtyLRNQHGWt zg8MhS@O87g<%gHx^g^&i9vY}yd=S?Q{%T-MLv(GB5B^s=*#8v-01^2=<`k|!59Zr^ zK|WT0zCp&4Aa4p|1Iq?INTIE?Re2yWpY-S99z|<6xk0mk_4NbK5!gKZz_y@Ye1sX! zD6Jl*jKI<)IqP6hx|aNR(TMMrweAf)y1pc15MI&|*K57B?hwzAGRnZ2+V!%)>N#Ta z^O2#_sQ`$dwH^!gnLLY`^nud+qOD$6h(8uwqeGL2IUk6EBS1>0-p+!4ji+%PK-igr zYOVZ)4)5gV+4P?3c%!5qSKC|dv&#KBjf2KgNz1bxcObicAS+2!5MjQFM)04N)2Pa= zqUm|XC8hEaTErJKS9gK%ffXP1J}Ib?71i`U*)6r?5m|4kPn8~(E}NNcRjt#oUY%c^ z;H`MgXvdeTjr9jbp{01xJg_vsz;~$BmGZWE!`FAuo5pVVTYjuLSh?;ufSu7TNh+NXLteJdmHiMktXo+1_?R;{;B(WDKP79q)gBaId(UKl^)9?4+ z_E}L6$H3N_dB~bxc!`aI(n{kRosLdLnfhZKZSQbu3 zY=K8tEI5lx3<~(`x#OE+*CC=*i9YZckB?GnFrU!n1v{xbcJw28>9%yWNf_r9G2tf- z1g?G~$K$;#|1{qKf~q3iH1f3V#BP*eD8tLz!_|SS%@eH|q3ZkN^8pJ7FbwQ8N4vrN zvd~xWw9*&I*jD9wWuITqcGCTGYz^MaB#l}*7s34Z9$CG6M%5lIZg$(HC^l{=fWeu2 zvfwoaA=6wGCnXN>=-)wX-Y;)CmXbwpn$oHzJ@-@rHjPNNsudxM<})hBTjo0JuZQsT z^77Jtex;F>HGvIqQasW-r$&n3!xT{?#=|8HX?1YAfBw0#vGE~O!9y@0`Dpm!cuT6z zmkB27muxHt#sM}o@x@PG0c4QsA%3O*3}h=$a;YTRLX$8S2z)j3EB=Q?g|b?t5-k^} zy7FpoQx&O1Y62wzL&H$2C5jv%325f7CGc!C02G&WW5w=%A1HGVU0xh(%s|VBj#M+6 zla+U~6OIAN2Eg!xrhadSQbDXiLmQ_r_`B-!&oj7 zG&!<;=L2XAFc1JbuwfScw)$Eok=!!pK(bGtu*CNOPoU9L>j3COR8({%9pK{rhm9G< zsMPnKTEOMPMXT&@dLguH~-&6!QMh9(mR4NrNYNMIzEp@KLY{}2>DyZeU_F86RZ_x!_ZVljif@OGT}+^uQ2ClI-=pg>`n^pa+FQQinpom7tNE+qVem2-Hn$@ULDQ627sB~Rq0&cqK^;X9Q z1Z}erxbV%pOH{)_Y$z}R0BZoSyQ1KpUr88Tq0k^hlXcoMKtdE>f9y{g~EHH%Wz8Wwzt)y97<=V9j zNl1eAU_==Re>DgzbHzKXza4-iM8Gu?tnCt?gfc)i=!{jDA?(+93+xx$6UZ*>cP#X-&p;kG+W zyB!#a94sxXYTAJ`l)yRHxE>3492qUEXt}{%{E()UrZi9^w=~BLfL7+4YT96sS>RNd z2y|DE18f_YYXY-K-DM$4W$?=>o)^F1wS#@+@b+s%gbdIc%MVr__af?mSY>T8dQ=;E zz)JB+7qI1sHN!-twK}^p&xh%NDD##rl@pv#V3XpY9cQW1R%t~a8wEIO@?>E>F>_&? zQx29BYLby-4l;h=NY3Ud6$^Ct=WY`w*rA69skERH&MSXO)@~#{HyL}y)-1bQ3)~M` z6Q4wfXD4J#(axJL@fvq*B#qa8qiTx%#w7yHys$bv#P}&t(_!lkL|DQvN8Ew{=xD`@ z%kA*t-j#I@mlzPTGZssR)kf3$$uzT@mMg*6ggmiWV8GDyR2ahHDjSACS1;wB>}<&V ze!mC5s;l`vNLxoRe@c=3CUSFXWSNW2#&UTJUaBjg;!&UbRKiBH&FxrfT`7>F28fSJ zxe(hCmxu018-5&a8P>p31IziVi*rL&w%R~AR0P*8jg*78_&wlSaT?ccxuYX$1p z;4QroKdGr-UAl-;Y3k1|+W^x%+Wsk*?$z7WOQIp0gRYE=(rcN@5*MI0HubYqe8eF`HQDn2C$vhI|SqVAKs_a|E}Fq`=noKE^m8~vh-M`q+E#chnnAwq@JE0Wf5!`Fr8fX z#;LmxZ+U8G+yVxogXKry%;s<8u_xO4kr=Tuo*ppz*?*RUoE0MzFz-UPH(EhG_{L?s zxOVC(((5hwSpjgmRFGx$sfn2W)%lU#>wPiq)c-)K&VGM%l#Kz|segvUpoX^?bQUeY zUIjG*YCU-mrF3>o(d(OY9JZj=qfSn^4^R#myucfMe5h07!7VqDd%(f3c>Pii#`a49 zwjDQrNyDJ?5{?0#34j+MV+-(V{r%UMbBr@;DuHj^e)Cub(CpOd(``6&&;*oWbXbKH z0H>b-gk=A46*4H6Mktu?$^MNG(gcb9?CxB$VmHnlj0r}}>7<~aD11dK^ zwOWCli<>_EVC)CzgC#9jwzjr90Ys+()eFFvRRDw+i?Ks9B(Ou-oUx=!qND=Aj=<{z zESnS5l>nXlEG8&NrT{$%q~;W>16G{~RDY@i{y_tf zaN_Ldz!F8MhZg)lR&Auq$5&v`Ll!EAmEeTqfH|OJC3=r}=%QO}=7r`WKHxK=ox#ct zp^(Sb+1?LEjZn@A_rH`Bgwo(F*CN+B>MELB-r+mv$CJTzOi68zuW{B_? zf8mmiz9$2Koa6#I<*vhb{Oyxp^EU{<%OK!6b+49~`+Ifa$4bqq94aZF3Xmn*5d8}) z9`80!1(FkJ)t9Oa<*`--GExsmsC}waP6U+{c7T`MtW@?s%0hn5F&g<*X-#|5*{Ox8 zn7aW#y9@3wv>g&2l#I0cyVk)6MK|2}2zfwLZJGcxpcz4!7`^7|)O+xX3B>mREZ^Va zY(}u_%$xc}n4L1{;;yvSE_%_S@W&26O4G2vaWAyQB8GehF;%bM^^bn=DP_Y0!!H8p z4KcK!b)}4$1z9{KRR9CEiNjS$H7K6SNIRE2@sKJ+1LR`}akk~irj-mxfwH;TYPs{y z;Vt+iM+5^}dMSQcBub=SS*@MFW_Bz$UYgJDh$#uS)Z`N)yL8` + +The CAN driver is based on hardware implementation. +Any available output-capablepins can be used for TX, RX, BUS-OFF, and CLKOUT signal lines. + +.. image:: img/twai_blockdiag.png + +The driver is accessed via the :ref:`esp32.CAN ` class:: + + from esp32 import CAN + can = CAN(0, tx=5, rx=4, mode=CAN.NORMAL, baudrate=500000) + can.setfilter(0, CAN.FILTER_ADDRESS, params=[0x123], extframe=False) # set a filter to receive messages with id = 0x102 + can.send([1,2,3], 0x102, extframe=False) # send a message with id 123 + can.recv() # receive message + + can.any() # returns True if there are any message to receive + can.info() # get information about the controller’s error states and TX and RX buffers + can.deinit() # turn off the can bus + can.clear_rx_queue() # clear messages in the FIFO + can.clear_tx_queue() # clear messages in the transmit buffer + + Real time clock (RTC) --------------------- diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index f0f0c8ef1f56e..883d38e1504fa 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -351,3 +351,265 @@ supports 32-bit signed integers and blobs. .. method:: NVS.commit() Commits changes made by *set_xxx* methods to flash. + +.. _esp32.CAN: + +class CAN -- controller area network communication bus +====================================================== + +CAN implements the standard CAN communications protocol. At +the physical level it consists of 2 lines: RX and TX. Note that +to connect the microcontroller to a CAN bus you must use a CAN transceiver +to convert the CAN logic signals from the microcontroller to the correct +voltage levels on the bus. + +Example usage (works without anything connected):: + + from esp32 import CAN + BAUDRATE_500k = 500000 + can = CAN(0, tx=5, rx=4, mode=CAN.NORMAL, baudrate=BAUDRATE_500k) + can.setfilter(0, CAN.FILTER_ADDRESS, params=[0x123], extframe=False) # set a filter to receive messages with id = 0x102 + can.send([1,2,3], 0x102, extframe=False) # send a message with id 123 + if can.any(): + can.recv() # receive message + + +Constructors +------------ + +.. class:: esp32.CAN(bus, ...) + + Construct a CAN object on the given bus(controller). *bus* must be 0 for ESP32. + With no additional parameters, the CAN object is created but not + initialised (it has the settings from the last initialisation of + the bus, if any). If extra arguments are given, the bus is initialised. + See :meth:`CAN.init` for parameters of initialisation. + + The physical pins of the CAN bus can be assigned during init. + +Methods +------- + +.. method:: CAN.init(mode, *, tx=5, rx=4, baudrate=500000, prescaler=8, sjw=3, bs1=15, bs2=4, auto_restart=False, tx_queue=1, rx_queue=1) + + Initialise the CAN bus with the given parameters: + + - *mode* is one of: NORMAL, LOOPBACK, SILENT, SILENT_LOOPBACK + - *tx* defines the gpio used for transmission + - *rx* defines the gpio used for receiving + - *baudrate* is used to define a standard speed. If it is defined, the *prescaler*, *sjw*, *bs1*, *bs2* + will be ignored. Standard speeds are 25000, 50000, 100000, 125000, 250000, 500000, 1000000. Some versions + of esp32 supports non-standard speeds: 1000, 5000, 10000, 12500, 16000, 20000. + - *prescaler* is used to set the duration of 1 time quanta; the time quanta + will be the input clock divided by the prescaler + - *sjw* is the resynchronisation jump width in units of the time quanta; + it can be 1, 2, 3, 4 + - *bs1* defines the location of the sample point in units of the time quanta; + it can be between 1 and 1024 inclusive + - *bs2* defines the location of the transmit point in units of the time quanta; + it can be between 1 and 16 inclusive + - *bus_off* defines the gpio used for BUS-OFF signal line(optional) + - *clkout* defines the gpio used for CLKOUT signal line(optional) + - *tx_queue* defines the number of waiting tx messages can be stored + - *rx_queue* defines the number of received messages can be stored + - *auto_restart* sets whether the controller will automatically try and restart + communications after entering the bus-off state; if this is disabled then + :meth:`~CAN.restart()` can be used to leave the bus-off state. + This parameter is currently not implemented and it must be set to False + + +.. method:: CAN.deinit() + + Turn off the CAN bus. + +.. method:: CAN.restart() + + Force a software restart of the CAN controller without resetting its + configuration. + + If the controller enters the bus-off state then it will no longer participate + in bus activity. If the controller is not configured to automatically restart + (see :meth:`~CAN.init()`) then this method can be used to trigger a restart, + and the controller will follow the CAN protocol to leave the bus-off state and + go into the error active state. + +.. method:: CAN.state() + + Return the state of the controller. The return value can be one of: + + - ``CAN.STOPPED`` -- the controller is completely off and reset; + - ``CAN.ERROR_ACTIVE`` -- the controller is on and in the Error Active state + (both TEC and REC are less than 96); + - ``CAN.BUS_OFF`` -- the controller is on but not participating in bus activity + (TEC overflowed beyond 255). + - ``CAN.RECOVERING`` -- the controller is under recover from bus-off state; + + +.. method:: CAN.info() + + Get information about the controller's error states and TX and RX buffers. + If *list* is provided then it should be a list object with at least 8 entries, + which will be filled in with the information. Otherwise a new list will be + created and filled in. In both cases the return value of the method is the + populated list. + + The values in the list are: + + - TEC value + - REC value + - number of times the controller enterted the Error Warning state (wrapped around to 0 after 65535) + - number of times the controller enterted the Error Passive state (wrapped around to 0 after 65535) + - number of times the controller enterted the Bus Off state (wrapped around to 0 after 65535) + - number of pending TX messages + - number of pending RX messages + + +.. method:: CAN.setfilter(bank, mode, params, *, rtr=False, extframe=False) + + Configure a filter bank: + + - *bank* is the filter bank that is to be configured (esp32 supports only 0 bank) + - *mode* is the mode the filter should operate in. + - *params* is an array of values the defines the filter. + The contents of the array depends on the *mode* and *extframe* arguments. + + +-----------------------+----------------------------------------------------------------------------+ + | *mode* | contents of *params* array | + +=======================+============================================================================+ + | CAN.FILTER_RAW_SINGLE | *params* will be copied in hardware variable | + | | and single_filter_mode will be selected | + | | In this mode, *bank* will be ignored | + +-----------------------+----------------------------------------------------------------------------+ + | CAN.FILTER_RAW_DUAL | *params* will be copied in hardware variable | + | | and single_filter_mode will be cleared | + | | In this mode, *bank* will be ignored | + +-----------------------+----------------------------------------------------------------------------+ + | CAN.FILTER_ADDRESS | *params* could be: | + | | | + | | If ``extframe=True`` and *params* length of 1 -- filter 29 bit identifier | + | | of message. | + | | | + | | if ``extframe=False``: | + | | | + | | * length of 1 filter 11 bit identifier of message | + | | * length of 2 filter 11 bit identifier and first byte of message | + | | * length of 3 filter 11 bit identifier first and second bytes of message | + +-----------------------+----------------------------------------------------------------------------+ + + - *rtr* For classic CAN controllers, this is an array of booleans that states if + a filter should accept a remote transmission request message. If this argument + is not given then it defaults to ``False`` for all entries. + +.. method:: CAN.clearfilter(bank) + + Clear and disables all filters + +.. method:: CAN.any(fifo) + + Return ``True`` if any message waiting on the FIFO, else ``False``. + +.. method:: CAN.recv(list=None, *, timeout=5000) + + Receive data on the bus: + + - *list* is an optional list object to be used as the return value + - *timeout* is the timeout in milliseconds to wait for the receive. + + Return value: A tuple containing four values. + + - The id of the message. + - A boolean that indicates if the message is an RTR message. + - Reserved. + - An array containing the data. + + If *list* is ``None`` then a new tuple will be allocated, as well as a new + bytes object to contain the data (as the fourth element in the tuple). + + If *list* is not ``None`` then it should be a list object with at least four + elements. The fourth element should be a memoryview object which is created + from either a bytearray or an array of type 'B' or 'b', and this array must + have enough room for at least 8 bytes. The list object will then be + populated with the first three return values above, and the memoryview object + will be resized inplace to the size of the data and filled in with that data. + The same list and memoryview objects can be reused in subsequent calls to + this method, providing a way of receiving data without using the heap. + For example:: + + buf = bytearray(8) + lst = [0, 0, 0, memoryview(buf)] + # No heap memory is allocated in the following call + can.recv(lst, timeout=0) + +*list* values are: + +- identifier of can packet (int) +- extended packet (bool) +- rtr packet (bool) +- data frame (0..8 bytes) + + +.. method:: CAN.send(data, id, *, timeout=0, rtr=False, extframe=false) + + Send a message on the bus: + + - *data* is the data to send (an integer to send, or a buffer object). + - *id* is the id of the message to be sent. + - *timeout* is the timeout in milliseconds to wait for the send. + - *rtr* is a boolean that specifies if the message shall be sent as + a remote transmission request. If *rtr* is True then only the length + of *data* is used to fill in the DLC slot of the frame; the actual + bytes in *data* are unused. + + If timeout is 0 the message is placed in a buffer and the method returns + immediately. If all three buffers are in use an exception is thrown. + If timeout is not 0, the method waits until the message is transmitted. + If the message can't be transmitted within the specified time an exception + is thrown. + + Return value: ``None``. + +.. method:: CAN.clear_tx_queue() + + Clear all messages from transmitting queue. + +.. method:: CAN.clear_rx_queue() + + Clear all messages from receiving queue. + + +Constants +--------- + +.. data:: CAN.NORMAL + CAN.LOOPBACK + CAN.SILENT + CAN.SILENT_LOOPBACK + + + The mode of the CAN bus used in :meth:`~CAN.init()`. + + +---------------------+---------------------------------------------+-------+-------+ + | *mode* | \ | STM32 | ESP32 | + +=====================+=============================================+=======+=======+ + | CAN.NORMAL | .. image:: img/can_mode_normal.png | + | + | + +---------------------+---------------------------------------------+-------+-------+ + | CAN.LOOPBACK | .. image:: img/can_mode_loopback.png | + | + | + +---------------------+---------------------------------------------+-------+-------+ + | CAN.SILENT | .. image:: img/can_mode_silent.png | + | + | + +---------------------+---------------------------------------------+-------+-------+ + | CAN.SILENT_LOOPBACK | .. image:: img/can_mode_silent_loopback.png | + | + | + +---------------------+---------------------------------------------+-------+-------+ + + +.. data:: CAN.STOPPED + CAN.ERROR_ACTIVE + CAN.BUS_OFF + CAN.RECOVERING + + Possible states of the CAN controller returned from :meth:`~CAN.state()`. + +.. data:: CAN.FILTER_RAW_SINGLE + CAN.FILTER_RAW_DUAL + CAN.FILTER_ADDRESS + + The operation mode of a filter used in :meth:`~CAN.setfilter()`. diff --git a/docs/library/img/can_mode_loopback.png b/docs/library/img/can_mode_loopback.png new file mode 100755 index 0000000000000000000000000000000000000000..73c3290fc5864a75e951f8af85389ccdc503969e GIT binary patch literal 3669 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 literal 0 HcmV?d00001 diff --git a/docs/library/img/can_mode_normal.png b/docs/library/img/can_mode_normal.png new file mode 100755 index 0000000000000000000000000000000000000000..4ac218a2aafe1dd3567ed8778b245d6dd97e7575 GIT binary patch literal 3513 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 UXY@q@FL{u;skKR+k!Rw+0hLWxivR!s literal 0 HcmV?d00001 diff --git a/docs/library/img/can_mode_silent.png b/docs/library/img/can_mode_silent.png new file mode 100755 index 0000000000000000000000000000000000000000..5b74491be0bfe1a0468dedb7b2b9ce1cf5c6ac41 GIT binary patch literal 3632 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 UDQ)f#{7!%{Cf3H)hHlaS1ADxUtN;K2 literal 0 HcmV?d00001 diff --git a/examples/esp32_can.py b/examples/esp32_can.py new file mode 100644 index 0000000000000..d30f31f261af3 --- /dev/null +++ b/examples/esp32_can.py @@ -0,0 +1,60 @@ +from esp32 import CAN +import time + + +def send_and_check(can_bus, name, id, expected_result=True, extended=False): + can_bus.clear_tx_queue() + can_bus.clear_rx_queue() + can_bus.send([], id, extframe=extended) + time.sleep_ms(100) + if can_bus.any() == expected_result: + print("{}: OK".format(name)) + if expected_result: + can_bus.recv() + else: + print("{}: FAILED".format(name)) + + +# 4 and 5 pins must be connected to each other, see documentation +dev = CAN(0, tx=5, rx=4, mode=CAN.SILENT_LOOPBACK, baudrate=50000) + +# Test send/receive message +print("Loopback Test: no filter - STD") +send_and_check(dev, "No filter", 0x100, True) + +# Set filter1 +print("Loopback Test: one filter - STD") +dev.setfilter(0, CAN.FILTER_ADDRESS, [0x101, 0]) +send_and_check(dev, "Passing Message", 0x101, True) +send_and_check(dev, "Blocked Message", 0x100, False) + +# Set filter2 +print("Loopback Test: second filter - STD") +dev.setfilter(0, CAN.FILTER_ADDRESS, [0x102, 0]) +send_and_check(dev, "Passing Message - Bank 1", 0x102, True) +send_and_check(dev, "Passing Message - Bank 0", 0x101, True) +send_and_check(dev, "Blocked Message", 0x100, False) + +# Remove filter +print("Loopback Test: clear filter - STD") +dev.clearfilter() +send_and_check(dev, "Passing Message - Bank 1", 0x102, True) +send_and_check(dev, "Passing Message - Bank 0", 0x101, True) +send_and_check(dev, "Passing any Message", 0x100, True) + +# Extended message tests +# Test send/receive message +print("Loopback Test: no filter - Extd") +send_and_check(dev, "No filter", 0x100, True, extended=True) + +# Set filter1 +print("Loopback Test: one filter - Extd") +dev.setfilter(0, CAN.FILTER_ADDRESS, [0x101], extframe=True) +send_and_check(dev, "Passing Message", 0x101, True, extended=True) +send_and_check(dev, "Blocked Message", 0x100, False, extended=True) + +# Remove filter +print("Loopback Test: clear filter - Extd") +dev.clearfilter() +send_and_check(dev, "Passing Message - Bank 0", 0x101, True, extended=True) +send_and_check(dev, "Passing any Message", 0x100, True, extended=True) diff --git a/ports/esp32/esp32_can.c b/ports/esp32/esp32_can.c new file mode 100644 index 0000000000000..f38d9ac1b5cdd --- /dev/null +++ b/ports/esp32/esp32_can.c @@ -0,0 +1,764 @@ +/* The MIT License (MIT) + * + * Copyright (c) 2019 Musumeci Salvatore + * Copyright (c) 2021 Ihor Nehrutsa + * Copyright (c) 2022 Yuriy Makarov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include + +#include "py/objarray.h" +#include "py/binary.h" +#include "py/runtime.h" +#include "py/mphal.h" +#include "py/mperrno.h" +#include "freertos/task.h" +#include "esp_idf_version.h" + +// CAN only for ESP-IDF v4.2 and higher because of "driver/twai.h" +#if (ESP_IDF_VERSION_MAJOR == 4) && (ESP_IDF_VERSION_MINOR >= 2) + +#include "driver/twai.h" +#include "esp_task.h" +#include "esp32_can.h" + +#if MICROPY_HW_ENABLE_CAN + +// TWAI_MODE_NORMAL - Normal operating mode where TWAI controller can send/receive/acknowledge messages +// TWAI_MODE_NO_ACK - Transmission does not require acknowledgment. Use this mode for self testing. // This mode is useful when self testing the TWAI controller (loopback of transmissions). +// TWAI_MODE_LISTEN_ONLY - The TWAI controller will not influence the bus (No transmissions or acknowledgments) but can receive messages. // This mode is suited for bus monitor applications. + +#define CAN_MODE_NORMAL TWAI_MODE_NORMAL +#define CAN_MODE_LOOPBACK TWAI_MODE_NO_ACK +#define CAN_MODE_SILENT TWAI_MODE_LISTEN_ONLY +#define CAN_MODE_SILENT_LOOPBACK (0x10) + +#define CAN_TASK_PRIORITY (ESP_TASK_PRIO_MIN + 1) +#define CAN_TASK_STACK_SIZE (1024) +#define CAN_DEFAULT_PRESCALER (8) +#define CAN_DEFAULT_SJW (3) +#define CAN_DEFAULT_BS1 (15) +#define CAN_DEFAULT_BS2 (4) +#define CAN_MAX_DATA_FRAME (8) + +// INTERNAL Deinitialize can +void can_deinit(const esp32_can_obj_t *self) { + check_esp_err(twai_stop()); + check_esp_err(twai_driver_uninstall()); + if (self->irq_handler != NULL) { + vTaskDelete(self->irq_handler); + } + self->config->initialized = false; +} + +// singleton CAN device object +esp32_can_config_t can_config = { + .general = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_2, GPIO_NUM_4, CAN_MODE_NORMAL), + .filter = TWAI_FILTER_CONFIG_ACCEPT_ALL(), + .timing = TWAI_TIMING_CONFIG_25KBITS(), + .initialized = false +}; + +STATIC esp32_can_obj_t esp32_can_obj = { + {&esp32_can_type}, + .config = &can_config +}; + +// INTERNAL FUNCTION Return status information +STATIC twai_status_info_t _esp32_hw_can_get_status() { + twai_status_info_t status; + check_esp_err(twai_get_status_info(&status)); + return status; +} + +// INTERNAL FUNCTION Reset can filter to defaults +void esp32_reset_can_filter(const esp32_can_obj_t *self) { + twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); + self->config->filter.single_filter = f_config.single_filter; + self->config->filter.acceptance_code = f_config.acceptance_code; + self->config->filter.acceptance_mask = f_config.acceptance_mask; +} + +STATIC void esp32_hw_can_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->config->initialized) { + qstr mode = MP_QSTR_LISTEN; + switch (self->config->general.mode) { + case CAN_MODE_LOOPBACK: + mode = MP_QSTR_LOOPBACK; + break; + case CAN_MODE_SILENT: + mode = MP_QSTR_SILENT; + break; + case CAN_MODE_NORMAL: + mode = MP_QSTR_NORMAL; + break; + } + mp_printf(print, "CAN(tx=%u, rx=%u, baudrate=%ukb, mode=%q, loopback=%u)", + self->config->general.tx_io, + self->config->general.rx_io, + self->config->baudrate, + mode, + self->loopback + ); + } else { + mp_printf(print, "Device is not initialized"); + } +} + +// INTERNAL FUNCTION FreeRTOS IRQ task +STATIC void esp32_hw_can_irq_task(void *self_in) { + esp32_can_obj_t *self = (esp32_can_obj_t *)self_in; + uint32_t alerts; + + twai_reconfigure_alerts( + TWAI_ALERT_RX_DATA | TWAI_ALERT_RX_QUEUE_FULL | TWAI_ALERT_BUS_OFF | TWAI_ALERT_ERR_PASS | + TWAI_ALERT_ABOVE_ERR_WARN | TWAI_ALERT_TX_FAILED | TWAI_ALERT_TX_SUCCESS | TWAI_ALERT_BUS_RECOVERED, + NULL + ); + + while (1) { + check_esp_err(twai_read_alerts(&alerts, portMAX_DELAY)); + + if (alerts & TWAI_ALERT_BUS_OFF) { + ++self->num_bus_off; + } + if (alerts & TWAI_ALERT_ERR_PASS) { + ++self->num_error_passive; + } + if (alerts & TWAI_ALERT_ABOVE_ERR_WARN) { + ++self->num_error_warning; + } + + if (alerts & (TWAI_ALERT_TX_FAILED | TWAI_ALERT_TX_SUCCESS)) { + self->last_tx_success = (alerts & TWAI_ALERT_TX_SUCCESS) > 0; + } + + if (alerts & (TWAI_ALERT_BUS_RECOVERED)) { + self->bus_recovery_success = true; + } + + if (self->rxcallback != mp_const_none) { + if (alerts & TWAI_ALERT_RX_DATA) { + uint32_t msgs_to_rx = _esp32_hw_can_get_status().msgs_to_rx; + + if (msgs_to_rx == 1) { + // first message in queue + mp_sched_schedule(self->rxcallback, MP_OBJ_NEW_SMALL_INT(0)); + } else if (msgs_to_rx >= self->config->general.rx_queue_len) { + // queue is full + mp_sched_schedule(self->rxcallback, MP_OBJ_NEW_SMALL_INT(1)); + } + } + if (alerts & TWAI_ALERT_RX_QUEUE_FULL) { + // queue overflow + mp_sched_schedule(self->rxcallback, MP_OBJ_NEW_SMALL_INT(2)); + } + } + } +} + +// init(mode, tx=5, rx=4, baudrate=500000, prescaler=8, sjw=3, bs1=15, bs2=4, auto_restart=False, tx_queue=1, rx_queue=1) +STATIC mp_obj_t esp32_hw_can_init_helper(esp32_can_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_mode, ARG_prescaler, ARG_sjw, ARG_bs1, ARG_bs2, ARG_auto_restart, ARG_baudrate, + ARG_tx_io, ARG_rx_io, ARG_tx_queue, ARG_rx_queue}; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_mode, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = CAN_MODE_NORMAL} }, + { MP_QSTR_prescaler, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = CAN_DEFAULT_PRESCALER} }, + { MP_QSTR_sjw, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = CAN_DEFAULT_SJW} }, + { MP_QSTR_bs1, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = CAN_DEFAULT_BS1} }, + { MP_QSTR_bs2, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = CAN_DEFAULT_BS2} }, + { MP_QSTR_auto_restart, MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_baudrate, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 500000} }, + { MP_QSTR_tx, MP_ARG_INT, {.u_int = 4} }, + { MP_QSTR_rx, MP_ARG_INT, {.u_int = 5} }, + { MP_QSTR_tx_queue, MP_ARG_INT, {.u_int = 1} }, + { MP_QSTR_rx_queue, MP_ARG_INT, {.u_int = 1} }, + }; + + // parse args + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Configure device + const int mode = args[ARG_mode].u_int; + self->loopback = false; + self->config->baudrate = args[ARG_baudrate].u_int; + if (mode == CAN_MODE_SILENT_LOOPBACK) { + self->config->general.mode = TWAI_MODE_NO_ACK; + self->loopback = true; + } else { + self->config->general.mode = mode & 0x0F; + } + self->config->general.tx_io = args[ARG_tx_io].u_int; + self->config->general.rx_io = args[ARG_rx_io].u_int; + self->config->general.clkout_io = TWAI_IO_UNUSED; + self->config->general.bus_off_io = TWAI_IO_UNUSED; + self->config->general.tx_queue_len = args[ARG_tx_queue].u_int; + self->config->general.rx_queue_len = args[ARG_rx_queue].u_int; + self->config->general.alerts_enabled = TWAI_ALERT_NONE; + self->config->general.clkout_divider = 0; + if (args[ARG_auto_restart].u_bool) { + mp_raise_NotImplementedError("Auto-restart not supported"); + } + esp32_reset_can_filter(self); + + // clear errors + self->num_error_warning = 0; + self->num_error_passive = 0; + self->num_bus_off = 0; + + // Calculate CAN nominal bit timing from baudrate if provided + twai_timing_config_t *timing; + switch ((int)args[ARG_baudrate].u_int) { + case 0: + timing = &((twai_timing_config_t) { + .brp = args[ARG_prescaler].u_int, + .sjw = args[ARG_sjw].u_int, + .tseg_1 = args[ARG_bs1].u_int, + .tseg_2 = args[ARG_bs2].u_int, + .triple_sampling = false + }); + break; + #ifdef TWAI_TIMING_CONFIG_1KBITS + case 1000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_1KBITS()); + break; + #endif + #ifdef TWAI_TIMING_CONFIG_5KBITS + case 5000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_5KBITS()); + break; + #endif + #ifdef TWAI_TIMING_CONFIG_10KBITS + case 10000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_10KBITS()); + break; + #endif + #ifdef TWAI_TIMING_CONFIG_12_5KBITS + case 12500: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_12_5KBITS()); + break; + #endif + #ifdef TWAI_TIMING_CONFIG_16KBITS + case 16000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_16KBITS()); + break; + #endif + #ifdef TWAI_TIMING_CONFIG_20KBITS + case 20000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_20KBITS()); + break; + #endif + case 25000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_25KBITS()); + break; + case 50000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_50KBITS()); + break; + case 100000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_100KBITS()); + break; + case 125000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_125KBITS()); + break; + case 250000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_250KBITS()); + break; + case 500000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_500KBITS()); + break; + case 800000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_800KBITS()); + break; + case 1000000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_1MBITS()); + break; + default: + mp_raise_ValueError("Unable to set baudrate"); + self->config->baudrate = 0; + return mp_const_none; + } + self->config->timing = *timing; + + check_esp_err(twai_driver_install(&self->config->general, &self->config->timing, &self->config->filter)); + check_esp_err(twai_start()); + if (xTaskCreatePinnedToCore(esp32_hw_can_irq_task, "can_irq_task", CAN_TASK_STACK_SIZE, self, CAN_TASK_PRIORITY, (TaskHandle_t *)&self->irq_handler, MP_TASK_COREID) != pdPASS) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("failed to create can irq task handler")); + } + self->config->initialized = true; + + return mp_const_none; +} + +// CAN(bus, ...) No argument to get the object +STATIC mp_obj_t esp32_hw_can_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // check arguments + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + if (mp_obj_is_int(args[0]) != true) { + mp_raise_TypeError("bus must be a number"); + } + + // work out port + mp_uint_t can_idx = mp_obj_get_int(args[0]); + if (can_idx != 0) { + mp_raise_msg_varg(&mp_type_ValueError, "CAN(%d) doesn't exist", can_idx); + } + + esp32_can_obj_t *self = &esp32_can_obj; + if (!self->config->initialized || n_args > 1 || n_kw > 0) { + if (self->config->initialized) { + // The caller is requesting a reconfiguration of the hardware + // this can only be done if the hardware is in init mode + can_deinit(self); + } + self->rxcallback = mp_const_none; + self->irq_handler = NULL; + + if (n_args > 1 || n_kw > 0) { + // start the peripheral + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + esp32_hw_can_init_helper(self, n_args - 1, args + 1, &kw_args); + } + } + return MP_OBJ_FROM_PTR(self); +} + +// init(tx, rx, baudrate, mode=CAN_MODE_NORMAL, tx_queue=2, rx_queue=5) +STATIC mp_obj_t esp32_hw_can_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + return esp32_hw_can_init_helper(MP_OBJ_TO_PTR(args[0]), n_args - 1, args + 1, kw_args); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_hw_can_init_obj, 1, esp32_hw_can_init); + +// deinit() +STATIC mp_obj_t esp32_hw_can_deinit(const mp_obj_t self_in) { + const esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + can_deinit(self); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_deinit_obj, esp32_hw_can_deinit); + +// Force a software restart of the controller, to allow transmission after a bus error +STATIC mp_obj_t esp32_hw_can_restart(mp_obj_t self_in) { + esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + twai_status_info_t status = _esp32_hw_can_get_status(); + if (!self->config->initialized || status.state != TWAI_STATE_BUS_OFF) { + mp_raise_ValueError(NULL); + } + + self->bus_recovery_success = -1; + check_esp_err(twai_initiate_recovery()); + + while (self->bus_recovery_success < 0) { + MICROPY_EVENT_POLL_HOOK + } + + if (self->bus_recovery_success) { + check_esp_err(twai_start()); + } else { + mp_raise_OSError(MP_EIO); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_restart_obj, esp32_hw_can_restart); + +// Get the state of the controller +STATIC mp_obj_t esp32_hw_can_state(mp_obj_t self_in) { + esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_int_t state = TWAI_STATE_STOPPED; + if (self->config->initialized) { + state = _esp32_hw_can_get_status().state; + } + return mp_obj_new_int(state); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_state_obj, esp32_hw_can_state); + +// info() -- Get info about error states and TX/RX buffers +STATIC mp_obj_t esp32_hw_can_info(size_t n_args, const mp_obj_t *args) { + esp32_can_obj_t *self = MP_OBJ_TO_PTR(args[0]); + mp_obj_list_t *list; + if (n_args == 1) { + list = MP_OBJ_TO_PTR(mp_obj_new_list(7, NULL)); + } else { + if (!mp_obj_is_type(args[1], &mp_type_list)) { + mp_raise_TypeError(NULL); + } + list = MP_OBJ_TO_PTR(args[1]); + if (list->len < 7) { + mp_raise_ValueError(NULL); + } + } + twai_status_info_t status = _esp32_hw_can_get_status(); + list->items[0] = MP_OBJ_NEW_SMALL_INT(status.tx_error_counter); + list->items[1] = MP_OBJ_NEW_SMALL_INT(status.rx_error_counter); + list->items[2] = MP_OBJ_NEW_SMALL_INT(self->num_error_warning); + list->items[3] = MP_OBJ_NEW_SMALL_INT(self->num_error_passive); + list->items[4] = MP_OBJ_NEW_SMALL_INT(self->num_bus_off); + list->items[5] = MP_OBJ_NEW_SMALL_INT(status.msgs_to_tx); + list->items[6] = MP_OBJ_NEW_SMALL_INT(status.msgs_to_rx); + return MP_OBJ_FROM_PTR(list); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_hw_can_info_obj, 1, 2, esp32_hw_can_info); + +// any() - return `True` if any message waiting, else `False` +STATIC mp_obj_t esp32_hw_can_any(mp_obj_t self_in) { + twai_status_info_t status = _esp32_hw_can_get_status(); + return mp_obj_new_bool((status.msgs_to_rx) > 0); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_any_obj, esp32_hw_can_any); + +// send([data], id, *, timeout=0, rtr=false, extframe=false) +STATIC mp_obj_t esp32_hw_can_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_data, ARG_id, ARG_timeout, ARG_rtr, ARG_extframe }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_data, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_rtr, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_extframe, MP_ARG_BOOL, {.u_bool = false} }, + }; + + // parse args + esp32_can_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // populate message + twai_message_t tx_msg; + + size_t length; + mp_obj_t *items; + mp_obj_get_array(args[ARG_data].u_obj, &length, &items); + if (length > CAN_MAX_DATA_FRAME) { + mp_raise_ValueError("CAN data field too long"); + } + tx_msg.data_length_code = length; + tx_msg.flags = (args[ARG_rtr].u_bool ? TWAI_MSG_FLAG_RTR : TWAI_MSG_FLAG_NONE); + + if (args[ARG_extframe].u_bool) { + tx_msg.identifier = args[ARG_id].u_int & 0x1FFFFFFF; + tx_msg.flags += TWAI_MSG_FLAG_EXTD; + } else { + tx_msg.identifier = args[ARG_id].u_int & 0x7FF; + } + if (self->loopback) { + tx_msg.flags += TWAI_MSG_FLAG_SELF; + } + + for (uint8_t i = 0; i < length; i++) { + tx_msg.data[i] = mp_obj_get_int(items[i]); + } + + if (_esp32_hw_can_get_status().state == TWAI_STATE_RUNNING) { + uint32_t timeout_ms = args[ARG_timeout].u_int; + + if (timeout_ms != 0) { + self->last_tx_success = -1; + uint32_t start = mp_hal_ticks_us(); + check_esp_err(twai_transmit(&tx_msg, pdMS_TO_TICKS(timeout_ms))); + while (self->last_tx_success < 0) { + if (timeout_ms != portMAX_DELAY) { + if (mp_hal_ticks_us() - start >= timeout_ms) { + mp_raise_OSError(MP_ETIMEDOUT); + } + } + MICROPY_EVENT_POLL_HOOK + } + + if (!self->last_tx_success) { + mp_raise_OSError(MP_EIO); + } + } else { + check_esp_err(twai_transmit(&tx_msg, portMAX_DELAY)); + } + + return mp_const_none; + } else { + mp_raise_msg(&mp_type_RuntimeError, "Device is not ready"); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_hw_can_send_obj, 3, esp32_hw_can_send); + +// recv(list=None, *, timeout=5000) +STATIC mp_obj_t esp32_hw_can_recv(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_list, ARG_timeout }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_list, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 5000} }, + }; + + // parse args + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // receive the data + twai_message_t rx_msg; + check_esp_err(twai_receive(&rx_msg, pdMS_TO_TICKS(args[ARG_timeout].u_int))); + uint32_t rx_dlc = rx_msg.data_length_code; + + // Create the tuple, or get the list, that will hold the return values + // Also populate the fifth element, either a new bytes or reuse existing memoryview + mp_obj_t ret_obj = args[ARG_list].u_obj; + mp_obj_t *items; + if (ret_obj == mp_const_none) { + ret_obj = mp_obj_new_tuple(4, NULL); + items = ((mp_obj_tuple_t *)MP_OBJ_TO_PTR(ret_obj))->items; + items[3] = mp_obj_new_bytes(rx_msg.data, rx_dlc); + } else { + // User should provide a list of length at least 5 to hold the values + if (!mp_obj_is_type(ret_obj, &mp_type_list)) { + mp_raise_TypeError(NULL); + } + mp_obj_list_t *list = MP_OBJ_TO_PTR(ret_obj); + if (list->len < 4) { + mp_raise_ValueError(NULL); + } + items = list->items; + // Fifth element must be a memoryview which we assume points to a + // byte-like array which is large enough, and then we resize it inplace + if (!mp_obj_is_type(items[3], &mp_type_memoryview)) { + mp_raise_TypeError(NULL); + } + mp_obj_array_t *mv = MP_OBJ_TO_PTR(items[3]); + if (!(mv->typecode == (MP_OBJ_ARRAY_TYPECODE_FLAG_RW | BYTEARRAY_TYPECODE) + || (mv->typecode | 0x20) == (MP_OBJ_ARRAY_TYPECODE_FLAG_RW | 'b'))) { + mp_raise_ValueError(NULL); + } + mv->len = rx_dlc; + memcpy(mv->items, rx_msg.data, rx_dlc); + } + + items[0] = MP_OBJ_NEW_SMALL_INT(rx_msg.identifier); + items[1] = rx_msg.extd ? mp_const_true : mp_const_false; + items[2] = rx_msg.rtr ? mp_const_true : mp_const_false; + + // Return the result + return ret_obj; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_hw_can_recv_obj, 0, esp32_hw_can_recv); + +// Clear filters setting +STATIC mp_obj_t esp32_hw_can_clearfilter(mp_obj_t self_in) { + esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Defaults from TWAI_FILTER_CONFIG_ACCEPT_ALL + self->config->filter.single_filter = true; + self->config->filter.acceptance_code = 0; + self->config->filter.acceptance_mask = 0xFFFFFFFF; + + // Apply filter + check_esp_err(twai_stop()); + check_esp_err(twai_driver_uninstall()); + check_esp_err(twai_driver_install( + &self->config->general, + &self->config->timing, + &self->config->filter)); + check_esp_err(twai_start()); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_clearfilter_obj, esp32_hw_can_clearfilter); + +// bank: 0 only +// mode: FILTER_RAW_SINGLE, FILTER_RAW_DUAL or FILTER_ADDR_SINGLE or FILTER_ADDR_DUAL +// params: [id, mask] +// rtr: ignored if FILTER_RAW +// Set CAN HW filter +STATIC mp_obj_t esp32_hw_can_setfilter(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_bank, ARG_mode, ARG_params, ARG_rtr, ARG_extframe }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_bank, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_mode, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_params, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_rtr, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_extframe, MP_ARG_BOOL, {.u_bool = false} }, + }; + + // parse args + esp32_can_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + const int can_idx = args[ARG_bank].u_int; + + if (can_idx != 0) { + mp_raise_msg_varg(&mp_type_ValueError, "Bank (%d) doesn't exist", can_idx); + } + + size_t len; + mp_obj_t *params; + mp_obj_get_array(args[ARG_params].u_obj, &len, ¶ms); + const int mode = args[ARG_mode].u_int; + + if (mode == FILTER_RAW_SINGLE || mode == FILTER_RAW_DUAL) { + if (len != 2) { + mp_raise_ValueError("params must be a 2-values list"); + } + self->config->filter.single_filter = (mode == FILTER_RAW_SINGLE); + self->config->filter.acceptance_code = mp_obj_get_int_truncated(params[0]); + self->config->filter.acceptance_mask = mp_obj_get_int_truncated(params[1]); + } else { + uint32_t code = 0x0; + uint32_t mask = 0xffffffff; + const bool has_rtr = args[ARG_rtr].u_obj != mp_const_none; + const bool rtr = args[ARG_rtr].u_obj == mp_const_true; + const bool extframe = args[ARG_extframe].u_bool; + + if (extframe) { + // bitmap: [29 bits id, rtr, 2 bits reserved] + if (len != 1) { + mp_raise_ValueError("params must be a 1-values list"); + } + code |= mp_obj_get_int_truncated(params[0]) << 3; // move to first 29 bits + mask &= ~(0x1fffffff << 3); // set first 29 bits to zeros + if (has_rtr) { + mask ^= 1 << 2; + const int rtr_bit = 1 << 2; + if (rtr) { + code |= rtr_bit; + } else { + code &= ~rtr_bit; + } + } + } else { + // bitmap: [11 bits id, rtr, 4 bits reserved, 8 bits data_byte_1, 8 bits data_byte_2] + if (len == 0) { + mp_raise_ValueError("params must not be empty"); + } + code |= mp_obj_get_int_truncated(params[0]) << 21; // move to first 11 bits + mask &= ~(0x7ff << 21); // set first 11 bits to zeros + if (has_rtr) { + mask ^= 1 << 20; + const int rtr_bit = 1 << 20; + if (rtr) { + code |= rtr_bit; + } else { + code &= ~rtr_bit; + } + } + // Set data filter bits + if (len > 1) { + // set data_byte_1 + code |= mp_obj_get_int_truncated(params[1]) << 8; + mask &= ~(0xff << 8); + } + if (len > 2) { + // set data_byte_2 + code |= mp_obj_get_int_truncated(params[2]) << 0; + mask &= ~(0xff << 0); + } + } + + // Always use single filter mode for addr filter + self->config->filter.single_filter = true; + self->config->filter.acceptance_code = code; + self->config->filter.acceptance_mask = mask; + } + + // Apply filter + check_esp_err(twai_stop()); + check_esp_err(twai_driver_uninstall()); + check_esp_err(twai_driver_install( + &self->config->general, + &self->config->timing, + &self->config->filter + )); + check_esp_err(twai_start()); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_hw_can_setfilter_obj, 1, esp32_hw_can_setfilter); + +// rxcallback(callable) +STATIC mp_obj_t esp32_hw_can_rxcallback(mp_obj_t self_in, mp_obj_t callback_in) { + esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (callback_in == mp_const_none) { + // disable callback + self->rxcallback = mp_const_none; + } else if (mp_obj_is_callable(callback_in)) { + // set up interrupt + self->rxcallback = callback_in; + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_hw_can_rxcallback_obj, esp32_hw_can_rxcallback); + +// Clear TX Queue +STATIC mp_obj_t esp32_hw_can_clear_tx_queue(mp_obj_t self_in) { + return mp_obj_new_bool(twai_clear_transmit_queue() == ESP_OK); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_clear_tx_queue_obj, esp32_hw_can_clear_tx_queue); + +// Clear RX Queue +STATIC mp_obj_t esp32_hw_can_clear_rx_queue(mp_obj_t self_in) { + return mp_obj_new_bool(twai_clear_receive_queue() == ESP_OK); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_clear_rx_queue_obj, esp32_hw_can_clear_rx_queue); + +STATIC const mp_rom_map_elem_t esp32_can_locals_dict_table[] = { + // CAN_ATTRIBUTES + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_CAN) }, + // Micropython Generic API + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&esp32_hw_can_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_hw_can_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_restart), MP_ROM_PTR(&esp32_hw_can_restart_obj) }, + { MP_ROM_QSTR(MP_QSTR_state), MP_ROM_PTR(&esp32_hw_can_state_obj) }, + { MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&esp32_hw_can_info_obj) }, + { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&esp32_hw_can_any_obj) }, + { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&esp32_hw_can_send_obj) }, + { MP_ROM_QSTR(MP_QSTR_recv), MP_ROM_PTR(&esp32_hw_can_recv_obj) }, + { MP_ROM_QSTR(MP_QSTR_setfilter), MP_ROM_PTR(&esp32_hw_can_setfilter_obj) }, + { MP_ROM_QSTR(MP_QSTR_clearfilter), MP_ROM_PTR(&esp32_hw_can_clearfilter_obj) }, + { MP_ROM_QSTR(MP_QSTR_rxcallback), MP_ROM_PTR(&esp32_hw_can_rxcallback_obj) }, + // ESP32 Specific API + { MP_OBJ_NEW_QSTR(MP_QSTR_clear_tx_queue), MP_ROM_PTR(&esp32_hw_can_clear_tx_queue_obj) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_clear_rx_queue), MP_ROM_PTR(&esp32_hw_can_clear_rx_queue_obj) }, + // CAN_MODE + { MP_ROM_QSTR(MP_QSTR_NORMAL), MP_ROM_INT(CAN_MODE_NORMAL) }, + { MP_ROM_QSTR(MP_QSTR_LOOPBACK), MP_ROM_INT(CAN_MODE_LOOPBACK) }, + { MP_ROM_QSTR(MP_QSTR_SILENT), MP_ROM_INT(CAN_MODE_SILENT) }, + { MP_ROM_QSTR(MP_QSTR_SILENT_LOOPBACK), MP_ROM_INT(CAN_MODE_SILENT_LOOPBACK) }, + // CAN_STATE + { MP_ROM_QSTR(MP_QSTR_STOPPED), MP_ROM_INT(TWAI_STATE_STOPPED) }, + { MP_ROM_QSTR(MP_QSTR_ERROR_ACTIVE), MP_ROM_INT(TWAI_STATE_RUNNING) }, + { MP_ROM_QSTR(MP_QSTR_BUS_OFF), MP_ROM_INT(TWAI_STATE_BUS_OFF) }, + { MP_ROM_QSTR(MP_QSTR_RECOVERING), MP_ROM_INT(TWAI_STATE_RECOVERING) }, + // CAN_FILTER_MODE + { MP_ROM_QSTR(MP_QSTR_FILTER_RAW_SINGLE), MP_ROM_INT(FILTER_RAW_SINGLE) }, + { MP_ROM_QSTR(MP_QSTR_FILTER_RAW_DUAL), MP_ROM_INT(FILTER_RAW_DUAL) }, + { MP_ROM_QSTR(MP_QSTR_FILTER_ADDRESS), MP_ROM_INT(FILTER_ADDRESS) }, +}; +STATIC MP_DEFINE_CONST_DICT(esp32_can_locals_dict, esp32_can_locals_dict_table); + +// Python object definition +MP_DEFINE_CONST_OBJ_TYPE( + esp32_can_type, + MP_QSTR_CAN, + MP_TYPE_FLAG_NONE, + make_new, esp32_hw_can_make_new, + print, esp32_hw_can_print, + locals_dict, (mp_obj_dict_t *)&esp32_can_locals_dict + ); + +#endif // MICROPY_HW_ENABLE_CAN + +#endif // ESP_IDF_VERSION diff --git a/ports/esp32/esp32_can.h b/ports/esp32/esp32_can.h new file mode 100644 index 0000000000000..e3fae88537f02 --- /dev/null +++ b/ports/esp32/esp32_can.h @@ -0,0 +1,68 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Musumeci Salvatore + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_ESP32_CAN_H +#define MICROPY_INCLUDED_ESP32_CAN_H + +#include "modmachine.h" +#include "freertos/task.h" + +#include "py/obj.h" + +#if MICROPY_HW_ENABLE_CAN + +#define DEVICE_NAME "CAN" + +typedef enum _filter_mode_t { + FILTER_RAW_SINGLE = 0, + FILTER_RAW_DUAL, + FILTER_ADDRESS +} filter_mode_t; + +typedef struct _esp32_can_config_t { + twai_timing_config_t timing; + twai_filter_config_t filter; + twai_general_config_t general; + uint32_t baudrate; // bit/s + bool initialized; +} esp32_can_config_t; + +typedef struct _esp32_can_obj_t { + mp_obj_base_t base; + esp32_can_config_t *config; + mp_obj_t rxcallback; + TaskHandle_t irq_handler; + bool loopback : 1; + byte last_tx_success : 1; + byte bus_recovery_success : 1; + uint16_t num_error_warning; + uint16_t num_error_passive; + uint16_t num_bus_off; +} esp32_can_obj_t; + +extern const mp_obj_type_t esp32_can_type; + +#endif // MICROPY_HW_ENABLE_CAN +#endif // MICROPY_INCLUDED_ESP32_CAN_H diff --git a/ports/esp32/main/CMakeLists.txt b/ports/esp32/main/CMakeLists.txt index f8acfa90524f4..380080483ea46 100644 --- a/ports/esp32/main/CMakeLists.txt +++ b/ports/esp32/main/CMakeLists.txt @@ -75,6 +75,7 @@ set(MICROPY_SOURCE_PORT ${PROJECT_DIR}/mpnimbleport.c ${PROJECT_DIR}/modsocket.c ${PROJECT_DIR}/modesp.c + ${PROJECT_DIR}/esp32_can.c ${PROJECT_DIR}/esp32_nvs.c ${PROJECT_DIR}/esp32_partition.c ${PROJECT_DIR}/esp32_rmt.c diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index 017db36e22adf..eedfc72bc4848 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -214,6 +214,9 @@ STATIC const mp_rom_map_elem_t esp32_module_globals_table[] = { #endif { MP_ROM_QSTR(MP_QSTR_idf_heap_info), MP_ROM_PTR(&esp32_idf_heap_info_obj) }, + #if (MICROPY_HW_ENABLE_CAN) && (ESP_IDF_VERSION_MAJOR == 4) && (ESP_IDF_VERSION_MINOR >= 2) + { MP_ROM_QSTR(MP_QSTR_CAN), MP_ROM_PTR(&esp32_can_type) }, + #endif { MP_ROM_QSTR(MP_QSTR_NVS), MP_ROM_PTR(&esp32_nvs_type) }, { MP_ROM_QSTR(MP_QSTR_Partition), MP_ROM_PTR(&esp32_partition_type) }, { MP_ROM_QSTR(MP_QSTR_RMT), MP_ROM_PTR(&esp32_rmt_type) }, diff --git a/ports/esp32/modesp32.h b/ports/esp32/modesp32.h index a685b7b38fe6f..09b8c611922ef 100644 --- a/ports/esp32/modesp32.h +++ b/ports/esp32/modesp32.h @@ -65,6 +65,7 @@ extern const mp_obj_type_t esp32_nvs_type; extern const mp_obj_type_t esp32_partition_type; extern const mp_obj_type_t esp32_rmt_type; extern const mp_obj_type_t esp32_ulp_type; +extern const mp_obj_type_t esp32_can_type; esp_err_t rmt_driver_install_core1(uint8_t channel_id); diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 94daa2caa175d..b083496102ba3 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -130,6 +130,7 @@ #ifndef MICROPY_HW_ENABLE_SDCARD #define MICROPY_HW_ENABLE_SDCARD (1) #endif +#define MICROPY_HW_ENABLE_CAN (1) #define MICROPY_HW_SOFTSPI_MIN_DELAY (0) #define MICROPY_HW_SOFTSPI_MAX_BAUDRATE (ets_get_cpu_frequency() * 1000000 / 200) // roughly #define MICROPY_PY_USSL (1) diff --git a/tests/esp32/can.py b/tests/esp32/can.py new file mode 100644 index 0000000000000..016a51e4ce06d --- /dev/null +++ b/tests/esp32/can.py @@ -0,0 +1,234 @@ +import time +import micropython +from array import array + +try: + from esp32 import CAN +except ImportError: + print("SKIP") + raise SystemExit + +# setup states +try: + can = CAN(0) + if can.state() != can.STOPPED: + CAN(0).deinit() +except (ValueError, OSError): + pass + + +def s2b(s): + return [ord(x) for x in s] + + +can = CAN(0) +print(can) + +# Test state when de-init'd +print(can.state() == can.STOPPED) + +can.init(mode=CAN.SILENT_LOOPBACK) +print(can) +print(can.any()) + +# Test state when freshly created +print(can.state() == can.ERROR_ACTIVE) + +# Test info returns a sensible value +print(can.info()) + +# Catch all filter +can.clearfilter() +can.send(s2b("A"), 123) + +can.send([25], 123) +time.sleep_ms(10) +print(can.any(), can.info()) +print(can.recv()) + +# Negative id +can.send(s2b("B"), -1) +print(can.recv()) + +# id overflow +can.send(s2b("C"), 0x7FF + 1) +print(can.recv()) + +# Test too long message +try: + can.send(s2b("D") * 9, 0x7FF) +except ValueError: + print("passed") +else: + print("failed") + +# Test that recv can work without allocating memory on the heap +buf = bytearray(10) +l = [0, 0, 0, memoryview(buf)] +l2 = None +message = s2b("E") + +micropython.heap_lock() + +can.send(message, 42) +l2 = can.recv(l) +assert l is l2 +print(l, len(l[3]), buf) + +message[0] = 0 +can.send(message, 42) +l2 = can.recv(l) +assert l is l2 +print(l, len(l[3]), buf) + +micropython.heap_unlock() + +# Test for non-list passed as second arg to recv +can.send(s2b("F"), 0) +try: + can.recv(0) +except TypeError: + print("TypeError") + can.clear_tx_queue() + +# Test for too-short-list passed as second arg to recv +can.send(s2b("F"), 1) +try: + can.recv([0, 0, 0]) +except ValueError: + print("ValueError") + can.clear_tx_queue() + +# Test for non-memoryview passed as 4th element to recv +can.send(s2b("F"), 1) +try: + can.recv([0, 0, 0, 0, 0]) +except TypeError: + print("TypeError") + can.clear_tx_queue() + +# Test for read-only-memoryview passed as 4th element to recv +can.send(s2b("F"), 1) +try: + can.recv([0, 0, 0, memoryview(bytes(8))]) +except ValueError: + print("ValueError") + can.clear_tx_queue() + +# Test for bad-typecode-memoryview passed as 4th element to recv +can.send(s2b("F"), 1) +try: + can.recv([0, 0, 0, memoryview(array("i", range(8)))]) +except ValueError: + print("ValueError") + can.clear_tx_queue() + +# Testing extended IDs +print("==== TEST extframe=True filter ====") + +# Catch all filter, but only for extframe's +# can.setfilter(0, CAN.MASK32, 0, (0, 0), extframe=True) +can.setfilter(0, CAN.FILTER_ADDRESS, [0x101], extframe=True) + +# matches filter +can.send(s2b("G"), 0x101, extframe=True) +time.sleep_ms(10) +print(can.any(), can.info()) +print(can.recv()) + +# not matches filter +can.send(s2b("G"), 0x100, extframe=True) +time.sleep_ms(10) +print(can.any(), can.info()) + +print("==== TEST extframe=False filter ====") +can.setfilter(0, CAN.FILTER_ADDRESS, [0x101], extframe=False) + +# matches filter +can.send(s2b("H"), 0x101, extframe=False) +time.sleep_ms(100) +print(can.any(), can.info()) +print(can.recv()) + +# not matches filter +can.send(s2b("H"), 0x100, extframe=False) +time.sleep_ms(10) +print(can.any(), can.info()) + +# not matches filter (extframe) +can.send(s2b("H"), 0x100, extframe=True) +time.sleep_ms(10) +print(can.any(), can.info()) + +# Std filter with data bytes +can.setfilter(0, CAN.FILTER_ADDRESS, [0x101, int.from_bytes(b"J", "little")], extframe=False) + +# matches filter +can.send(s2b("J"), 0x101, extframe=False) +time.sleep_ms(10) +print(can.any(), can.info()) +print(can.recv()) + +# not matches filter bt content +can.send(s2b("1"), 0x101, extframe=False) +time.sleep_ms(10) +print(can.any(), can.info()) + +can.deinit() + +print("==== TEST rx callbacks ====") + +can = CAN(0, mode=CAN.SILENT_LOOPBACK, tx_queue=5, rx_queue=3) + + +def cb0(reason): + if reason == 0: + print("pending") + if reason == 1: + print("full") + if reason == 2: + print("overflow") + + +can.rxcallback(cb0) + +can.send(s2b("K1"), 0x101, timeout=100) +time.sleep_ms(100) +can.send(s2b("K2"), 0x101, timeout=100) +time.sleep_ms(100) +can.send(s2b("K3"), 0x101, timeout=100) +time.sleep_ms(100) +can.send(s2b("K4"), 0x101, timeout=100) +time.sleep_ms(100) +print(can.any() and can.recv()) +print(can.any() and can.recv()) +print(can.any() and can.recv()) +# rx_queue is 3, so the last one is None +print(can.any() and can.recv()) + +can.rxcallback(None) + +# Testing asynchronous send +print("==== TEST async send ====") +can.send(s2b("L"), 1, timeout=0) +print(can.any()) +while not can.any(): + pass + +print(can.recv()) + +# Testing rtr messages +print("==== TEST rtr messages ====") +can.send([], 1, rtr=True) +time.sleep_ms(10) +print(can.any() and can.recv()) + +# test HAL error, timeout +print("==== TEST errors ====") + +can = CAN(0, mode=CAN.NORMAL) +try: + # can.send([], 1, timeout=10) + can.recv(timeout=10) +except OSError as e: + print(repr(e)) diff --git a/tests/esp32/can.py.exp b/tests/esp32/can.py.exp new file mode 100644 index 0000000000000..bafb126649bb2 --- /dev/null +++ b/tests/esp32/can.py.exp @@ -0,0 +1,45 @@ +Device is not initialized +True +CAN(tx=4, rx=5, baudrate=500000kb, mode=LOOPBACK, loopback=1) +False +True +[0, 0, 0, 0, 0, 0, 0] +True [0, 0, 0, 0, 0, 0, 1] +(123, False, False, b'A') +(2047, False, False, b'B') +(0, False, False, b'C') +passed +[42, False, False, ] 1 bytearray(b'E\x00\x00\x00\x00\x00\x00\x00\x00\x00') +[42, False, False, ] 1 bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') +TypeError +ValueError +TypeError +ValueError +ValueError +==== TEST extframe=True filter ==== +True [0, 0, 0, 0, 0, 0, 1] +(257, True, False, b'G') +False [0, 0, 0, 0, 0, 0, 0] +==== TEST extframe=False filter ==== +True [0, 0, 0, 0, 0, 0, 1] +(257, False, False, b'H') +False [0, 0, 0, 0, 0, 0, 0] +False [0, 0, 0, 0, 0, 0, 0] +True [0, 0, 0, 0, 0, 0, 1] +(257, False, False, b'J') +False [0, 0, 0, 0, 0, 0, 0] +==== TEST rx callbacks ==== +pending +full +overflow +(257, False, False, b'K1') +(257, False, False, b'K2') +(257, False, False, b'K3') +False +==== TEST async send ==== +False +(1, False, False, b'L') +==== TEST rtr messages ==== +(1, False, True, b'') +==== TEST errors ==== +OSError(116, 'ESP_ERR_TIMEOUT')