wii_ec_nunchuck.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. #include <stdint.h>
  2. #include <furi.h> // Core API
  3. #include "wii_anal.h"
  4. #include "wii_i2c.h"
  5. #include "bc_logging.h"
  6. #include "gfx/images.h" // Images
  7. #include "wii_anal_lcd.h" // Drawing functions
  8. #include "wii_anal_keys.h" // key mappings
  9. // ** If you want to see what this source code looks like with all the MACROs expanded
  10. // ** grep -v '#include ' wii_ec_nunchuck.c | gcc -E -o /dev/stdout -xc -
  11. # include "wii_ec_macros.h"
  12. //+============================================================================ ========================================
  13. // Standard Nunchuck : 2 buttons, 1 analogue joystick, 1 3-axis accelerometer
  14. //
  15. void nunchuck_decode (wiiEC_t* const pec)
  16. {
  17. ecDecNunchuck_t* p = &pec->dec[(pec->decN = !pec->decN)].nunchuck;
  18. uint8_t* joy = pec->joy;
  19. p->btnC = !(joy[5] & 0x02); // !{1}
  20. p->btnZ = !(joy[5] & 0x01); // !{1}
  21. p->joyX = joy[0]; // {8}
  22. p->joyY = joy[1]; // {8}
  23. p->accX = ((uint16_t)joy[2] << 2) | ((joy[5] >>2) & 0x03); // {10}
  24. p->accY = ((uint16_t)joy[3] << 2) | ((joy[5] >>4) & 0x03); // {10}
  25. p->accZ = ((uint16_t)joy[4] << 2) | ((joy[5] >>6) & 0x03); // {10}
  26. DEBUG(">%d> C:%c, Z:%c, Joy{x:%02X, y:%02X}, Acc{x:%03X, y:%03X, z:%03X}", pec->decN,
  27. (p->btnC ? '#' : '.'), (p->btnZ ? '#' : '.'),
  28. p->joyX, p->joyY, p->accX, p->accY, p->accZ
  29. );
  30. }
  31. //+============================================================================ ========================================
  32. // Give each button a unique character identifier
  33. //
  34. void nunchuck_msg (wiiEC_t* const pec, FuriMessageQueue* const queue)
  35. {
  36. ecDecNunchuck_t* new = &pec->dec[pec->decN].nunchuck;
  37. ecDecNunchuck_t* old = &pec->dec[!pec->decN].nunchuck;
  38. eventMsg_t msg = {
  39. .id = EVID_WIIEC,
  40. .wiiEc = {
  41. .type = WIIEC_NONE,
  42. .in = ' ',
  43. .val = 0,
  44. }
  45. };
  46. BUTTON(btnC, 'c');
  47. BUTTON(btnZ, 'z');
  48. ANALOG(joyX, 'x');
  49. ANALOG(joyY, 'y');
  50. ACCEL(accX, 'x');
  51. ACCEL(accY, 'y');
  52. ACCEL(accZ, 'z');
  53. }
  54. //+============================================================================ ========================================
  55. // https://www.hackster.io/infusion/using-a-wii-nunchuk-with-arduino-597254#toc-5--read-actual-calibration-data-from-the-device-14
  56. //
  57. void nunchuck_calib (wiiEC_t* const pec, ecCalib_t c)
  58. {
  59. ecDecNunchuck_t* src = &pec->dec[pec->decN].nunchuck; // from input
  60. ecCalNunchuck_t* dst = pec->calS.nunchuck; // to calibration data
  61. if (c & CAL_RESET) { // initialise ready for software calibration
  62. // LO is set to the MAXIMUM value (so it can be reduced)
  63. // HI is set to ZERO (so it can be increased)
  64. RESET_LO_HI(accX, 10); // 10bit value
  65. RESET_LO_HI(accY, 10); // 10bit value
  66. RESET_LO_HI(accZ, 10); // 10bit value
  67. RESET_LO_HI(joyX, 8); // 8bit value
  68. RESET_LO_HI(joyY, 8); // 8bit value
  69. }
  70. if (c & CAL_FACTORY) { // (re)set to factory defaults
  71. //! "[4] LSB of Zero value of X,Y,Z axes" ...helpful!
  72. //! ...Well, my test nunchuck has bits set in the bottom 6 bits, so let's guess ;)
  73. // No value available - annecdotal tests suggest 8 is reasonable
  74. FACTORY_LO( accX, 8);
  75. FACTORY_LO( accY, 8);
  76. FACTORY_LO( accZ, 8);
  77. // @ 0G
  78. FACTORY_MID( accX, ((pec->calF[0] <<2) | ((pec->calF[3] >>4) &0x3)) ) ;
  79. FACTORY_MID( accY, ((pec->calF[1] <<2) | ((pec->calF[3] >>2) &0x3)) ) ;
  80. FACTORY_MID( accZ, ((pec->calF[2] <<2) | ((pec->calF[3] ) &0x3)) ) ;
  81. // @ 1G
  82. FACTORY_HI( accX, ((pec->calF[4] <<2) | ((pec->calF[7] >>4) &0x3)) ) ;
  83. FACTORY_HI( accY, ((pec->calF[5] <<2) | ((pec->calF[7] >>2) &0x3)) ) ;
  84. FACTORY_HI( accZ, ((pec->calF[6] <<2) | ((pec->calF[7] ) &0x3)) ) ;
  85. // Joysticks
  86. FACTORY_LO( joyX, pec->calF[ 9] ) ;
  87. FACTORY_MID(joyX, pec->calF[10] ) ;
  88. FACTORY_HI( joyX, pec->calF[ 8] ) ;
  89. FACTORY_LO( joyY, pec->calF[12] ) ;
  90. FACTORY_MID(joyY, pec->calF[13] ) ;
  91. FACTORY_HI( joyY, pec->calF[11] ) ;
  92. }
  93. if (c & CAL_TRACK) { // track maximum and minimum values seen
  94. TRACK_LO_HI(accX);
  95. TRACK_LO_HI(accY);
  96. TRACK_LO_HI(accZ);
  97. TRACK_LO_HI(joyX);
  98. TRACK_LO_HI(joyY);
  99. }
  100. if (c & CAL_RANGE) { // perform software calibration step
  101. RANGE_LO_HI(accX);
  102. RANGE_LO_HI(accY);
  103. RANGE_LO_HI(accZ);
  104. if (!(c & CAL_NOTJOY)) { // double negative!
  105. RANGE_LO_HI(joyX);
  106. RANGE_LO_HI(joyY);
  107. }
  108. }
  109. if (c & CAL_CENTRE) { // reset centre point of joystick
  110. CENTRE(accX);
  111. CENTRE(accY);
  112. CENTRE(accZ);
  113. CENTRE(joyX);
  114. CENTRE(joyY);
  115. }
  116. }
  117. //============================================================================= ========================================
  118. // Accelerometer screen ...might this be useful for other controllers?
  119. //
  120. // https://bootlin.com/labs/doc/nunchuk.pdf
  121. // X : Move Left/Right : -left / +right
  122. // Y : Move Fwd/Bkwd : -fwd / +bkwd
  123. // Z : Move Down/Up : -down / +up
  124. //
  125. // Movement in the direction of an axis changes that axis reading
  126. // Twisting/tilting around an axis changes the other two readings
  127. //
  128. // EG. Move left will effect X ; turn left will effect Y & Z
  129. //
  130. #define aw 110 // axis width
  131. #define ah 15 // height {0......7......14}
  132. #define am 7 // midpoint { 7 }
  133. #define ar 7 // range {1234567 1234567}
  134. enum {
  135. ACC_X = 0,
  136. ACC_Y = 1,
  137. ACC_Z = 2,
  138. ACC_CNT = 3,
  139. ACC_1 = ACC_X, // first
  140. ACC_N = ACC_Z, // last
  141. };
  142. //+============================================================================
  143. static
  144. void nunchuck_showAcc (Canvas* const canvas, state_t* const state)
  145. {
  146. ecDecNunchuck_t* d = &state->ec.dec[state->ec.decN].nunchuck;
  147. ecCalNunchuck_t* lo = &state->ec.calS.nunchuck[1];
  148. ecCalNunchuck_t* mid = &state->ec.calS.nunchuck[2];
  149. ecCalNunchuck_t* hi = &state->ec.calS.nunchuck[3];
  150. int y[ACC_CNT] = {0, 0+(ah+4), 0+((ah+4)*2)};
  151. int x = 10;
  152. static uint16_t v[ACC_CNT][aw] = {0};
  153. // static uint16_t tv[ACC_CNT][aw] = {0};
  154. static uint16_t idx = 0;
  155. static uint16_t cnt = aw -1;
  156. // Only record when scanner NOT-paused
  157. if (!state->pause) {
  158. uint16_t dead = (1<<5);
  159. // Find axes y-offsets
  160. for (int a = ACC_1; a <= ACC_N; a++) {
  161. uint16_t* dp = NULL; // data value (current reading)
  162. uint16_t* lp = NULL; // lo value
  163. uint16_t* mp = NULL; // mid value
  164. uint16_t* hp = NULL; // hi value
  165. uint16_t* vp = NULL; // value (result)
  166. switch (a) {
  167. case ACC_X:
  168. dp = & d->accX; // data (input)
  169. lp = & lo->accX; // low \.
  170. mp = &mid->accX; // mid > calibration
  171. hp = & hi->accX; // high /
  172. vp = &v[ ACC_X][idx]; // value (where to store the result)
  173. break;
  174. case ACC_Y:
  175. dp = & d->accY;
  176. lp = & lo->accY;
  177. mp = &mid->accY;
  178. hp = & hi->accY;
  179. vp = &v[ ACC_Y][idx];
  180. break;
  181. case ACC_Z:
  182. dp = & d->accZ;
  183. lp = & lo->accZ;
  184. mp = &mid->accZ;
  185. hp = & hi->accZ;
  186. vp = &v[ ACC_Z][idx];
  187. break;
  188. default: break ;
  189. }
  190. // Again - qv. the joysick calibration:
  191. // This is not the "right way" to do this, it is just "one way" to do it
  192. // ...mid point and extreme zones have a deadzone
  193. // ...the rest is evenly divided by the amount of space on the graph
  194. if ((*dp >= (*mp -dead)) && (*dp <= (*mp +dead))) *vp = ar ;
  195. else if (*dp >= (*hp -dead)) *vp = ah-1 ;
  196. else if (*dp <= (*lp +dead)) *vp = 0 ;
  197. else if (*dp < *mp) {
  198. uint16_t min = ((*lp +dead) +1);
  199. uint16_t max = ((*mp -dead) -1);
  200. float range = (max -min) +1;
  201. float m = range /(ar-1); // 6 evenly(/fairly) divided zones
  202. *vp = ((int)((*dp -min) /m)) +1;
  203. } else {//if (*dp > *mp)
  204. uint16_t min = ((*mp +dead) +1);
  205. uint16_t max = ((*hp -dead) -1);
  206. float range = (max -min) +1;
  207. float m = range /(ar-1); // 6 evenly(/fairly) divided zones
  208. *vp = ((int)((*dp -min) /m)) +1 +ar;
  209. }
  210. }
  211. //! If we decide to offer "export to CSV"
  212. //! I suggest we keep a second array of true-values, rather than do all the maths every time
  213. //! Also - the data will need to me moved to the 'state' table - so a.n.other function can save it off
  214. // tv[ACC_X][idx] = d->accX;
  215. // tv[ACC_Y][idx] = d->accY;
  216. // tv[ACC_Z][idx] = d->accZ;
  217. // Prepare for the next datapoint
  218. if (++idx >= aw) idx = 0 ;
  219. if (cnt) cnt-- ;
  220. }
  221. // Auto-pause
  222. if (state->apause && !idx) state->pause = true ;
  223. // *** Draw axes ***
  224. show(canvas, 0,y[ACC_X] +((ah -img_6x8_X.h) /2), &img_6x8_X, SHOW_SET_BLK);
  225. show(canvas, 0,y[ACC_Y] +((ah -img_6x8_Y.h) /2), &img_6x8_Y, SHOW_SET_BLK);
  226. show(canvas, 0,y[ACC_Z] +((ah -img_6x8_Z.h) /2), &img_6x8_Z, SHOW_SET_BLK);
  227. canvas_set_color(canvas, ColorBlack);
  228. for (int a = ACC_1; a <= ACC_N; a++) {
  229. canvas_draw_line(canvas, x-1,y[a] , x -1,y[a]+ah);
  230. canvas_draw_line(canvas, x ,y[a]+ah, x+aw-1,y[a]+ah);
  231. // Mid & Peak lines
  232. for (int i = 1; i < aw; i += 3) {
  233. canvas_draw_dot(canvas, x+i,y[a]);
  234. canvas_draw_dot(canvas, x+i,y[a] +(ah /2));
  235. }
  236. }
  237. // Data (wiper display - see notes.txt for scrolling algorithm)
  238. int end = idx ? idx : aw;
  239. for (int a = ACC_1; a <= ACC_N; a++) {
  240. canvas_draw_dot(canvas, x,y[a]+v[a][idx]);
  241. for (int i = 1; i < end; i++)
  242. canvas_draw_line(canvas, x+i,y[a]+v[a][i-1] , x+i,y[a]+v[a][i]);
  243. if (!state->apause)
  244. for (int i = end+10; i < aw -cnt; i++)
  245. canvas_draw_line(canvas, x+i,y[a]+v[a][i-1] , x+i,y[a]+v[a][i]);
  246. }
  247. // Wipe bar
  248. if (end < aw) canvas_draw_line(canvas, x+end,y[0], x+end,y[2]+ah-1);
  249. if (++end < aw) canvas_draw_line(canvas, x+end,y[0], x+end,y[2]+ah-1);
  250. if (++end < aw) canvas_draw_line(canvas, x+end,y[0], x+end,y[2]+ah-1);
  251. // *** Mode buttons ***
  252. show(canvas, 0,55, &img_key_L, SHOW_SET_BLK); // mode key
  253. if ((state->calib & CAL_RANGE) || state->pause) state->flash++ ;
  254. // -pause- ...yeah, this got a little out of hand! LOL!
  255. if (state->pause || state->apause) {
  256. if (state->pause && state->apause && !idx) {
  257. if (state->flash &8) {
  258. show(canvas, 108,56, &img_key_U, SHOW_SET_BLK);
  259. } else {
  260. show(canvas, 108,56, &img_key_Ui, SHOW_SET_BLK);
  261. canvas_draw_line(canvas, x+aw,y[0], x+aw,y[2]+ah-1);
  262. }
  263. } else {
  264. show(canvas, 108,56, &img_key_Ui, SHOW_SET_BLK);
  265. }
  266. } else {
  267. show(canvas, 108,56, &img_key_U, SHOW_SET_BLK); // pause
  268. }
  269. // -calibration-
  270. if (state->calib & CAL_RANGE) {
  271. show(canvas, 119,55, (state->flash &8) ? &img_key_OKi : &img_key_OK, SHOW_SET_BLK);
  272. } else {
  273. show(canvas, 119,55, &img_key_OK, SHOW_SET_BLK);
  274. }
  275. }
  276. # undef aw
  277. # undef ah
  278. # undef am
  279. # undef ar
  280. //+============================================================================ ========================================
  281. // Default nunchuck screen
  282. //
  283. void nunchuck_show (Canvas* const canvas, state_t* const state)
  284. {
  285. // Nunchucks have TWO scenes
  286. if (state->scene == SCENE_NUNCHUCK_ACC) return nunchuck_showAcc(canvas, state) ;
  287. // Default scene
  288. ecDecNunchuck_t* d = &state->ec.dec[state->ec.decN].nunchuck;
  289. ecCalNunchuck_t* c = (state->hold) ? &state->ec.calS.nunchuck[(state->hold < 0) ? 0 : 4]
  290. : (ecCalNunchuck_t*)d ; //! danger will robinson!
  291. ecCalNunchuck_t* js = state->ec.calS.nunchuck;
  292. // X, Y, Z
  293. show(canvas, 42,0, &img_6x8_X, SHOW_SET_BLK);
  294. show(canvas, 73,0, &img_6x8_Y, SHOW_SET_BLK);
  295. show(canvas, 104,0, &img_6x8_Z, SHOW_SET_BLK);
  296. canvas_draw_str_aligned(canvas, 0,14, AlignLeft, AlignTop, "Accel");
  297. canvas_draw_str_aligned(canvas, 0,28, AlignLeft, AlignTop, "Joy");
  298. // accel values
  299. showHex(canvas, 34,12, c->accX, 3,2);
  300. showHex(canvas, 65,12, c->accY, 3,2);
  301. showHex(canvas, 96,12, c->accZ, 3,2);
  302. // Joy values
  303. showHex(canvas, 38,27, c->joyX, 2,2);
  304. showHex(canvas, 69,27, c->joyY, 2,2);
  305. showJoy(canvas, 103,32, js[1].joyX, js[2].joyX, js[3].joyX,
  306. js[1].joyY, js[2].joyY, js[3].joyY, d->joyX,d->joyY, 8);
  307. // buttons
  308. canvas_set_color(canvas, ColorBlack);
  309. canvas_draw_str_aligned(canvas, 0,44, AlignLeft, AlignTop, "Button");
  310. if (!d->btnC) {
  311. canvas_draw_rframe(canvas, 36,42, 18,12, 6);
  312. show(canvas, 42,44, &img_6x8_C, SHOW_SET_BLK);
  313. } else {
  314. canvas_draw_rbox(canvas, 36,42, 18,12, 6);
  315. show(canvas, 42,44, &img_6x8_C, SHOW_SET_WHT);
  316. canvas_set_color(canvas, ColorBlack);
  317. }
  318. if (!d->btnZ) {
  319. canvas_draw_rframe(canvas, 64,40, 24,16, 2);
  320. show(canvas, 73,44, &img_6x8_Z, SHOW_SET_BLK);
  321. } else {
  322. canvas_draw_rbox(canvas, 64,40, 24,16, 2);
  323. show(canvas, 73,44, &img_6x8_Z, SHOW_SET_WHT);
  324. }
  325. // Navigation
  326. showPeakHold(state, canvas, state->hold); // peak keys
  327. show(canvas, 0,55, &img_key_L, SHOW_SET_BLK); // mode keys
  328. show(canvas, 9,55, &img_key_R, SHOW_SET_BLK);
  329. }
  330. //+============================================================================ ========================================
  331. static
  332. bool nunchuck_keyAcc (const eventMsg_t* const msg, state_t* const state)
  333. {
  334. int used = false; // assume key is NOT-handled
  335. switch (msg->input.type) {
  336. case InputTypeShort: //# <! After InputTypeRelease within INPUT_LONG_PRESS interval
  337. switch (msg->input.key) {
  338. case InputKeyDown: //# <D [ SHORT-DOWN ]
  339. used = true; // Block trough-hold
  340. break;
  341. case InputKeyUp: //# <U [ SHORT-UP ]
  342. if (state->pause) state->pause = false ; // Paused? Restart
  343. else state->apause = !state->apause ; // No? toggle auto-pause
  344. used = true;
  345. break;
  346. case InputKeyLeft: //# <L [ SHORT-LEFT ]
  347. sceneSet(state, SCENE_NUNCHUCK);
  348. state->calib &= ~CAL_NOTJOY; // DO calibrate joystick in NUNCHUCK mode
  349. used = true;
  350. break;
  351. default: break ; //# <?
  352. }
  353. break;
  354. default: break ;
  355. }
  356. // Calibration keys
  357. if (!used) used = key_calib(msg, state) ;
  358. return used;
  359. }
  360. //+============================================================================ ========================================
  361. bool nunchuck_key (const eventMsg_t* const msg, state_t* const state)
  362. {
  363. // Nunchucks have TWO scenes
  364. if (state->scene == SCENE_NUNCHUCK_ACC) return nunchuck_keyAcc(msg, state) ;
  365. // Default scene
  366. int used = false; // assume key is NOT-handled
  367. switch (msg->input.type) {
  368. case InputTypeShort: //# <! After InputTypeRelease within INPUT_LONG_PRESS interval
  369. switch (msg->input.key) {
  370. case InputKeyLeft: //# <L [ SHORT-LEFT ]
  371. sceneSet(state, SCENE_DUMP);
  372. used = true;
  373. break;
  374. case InputKeyRight: //# <R [ SHORT-RIGHT ]
  375. sceneSet(state, SCENE_NUNCHUCK_ACC);
  376. state->calib |= CAL_NOTJOY; // do NOT calibrate joystick in _ACC mode
  377. used = true;
  378. break;
  379. default: break ; //# <?
  380. }
  381. break;
  382. default: break ;
  383. }
  384. // Calibration keys
  385. if (!used) used = key_calib(msg, state) ;
  386. return used;
  387. }