wii_ec_nunchuck.c 16 KB

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