3D printed Arduino Airsoft Chronograph
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

lcd.cpp 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. /*
  2. * lcd.cpp
  3. *
  4. * OpenChrono BB speed measurement device.
  5. *
  6. * Copyright (c) 2022 Thomas Buck <thomas@xythobuz.de>
  7. *
  8. * SSD1306 OLED display connected via I2C.
  9. */
  10. #include <Arduino.h>
  11. #include "ticks.h"
  12. #include "adc.h"
  13. #include "lcd.h"
  14. #include "config.h"
  15. #include <Wire.h>
  16. #include <U8g2lib.h>
  17. static uint8_t screens[] = SCREEN_ROTATION;
  18. static uint8_t lcd_screen = 0;
  19. static uint64_t lcd_rotate_time = 0;
  20. static LCD_TYPE u8g2(U8G2_R0, U8X8_PIN_NONE);
  21. void lcd_init(void) {
  22. u8g2.begin();
  23. u8g2.setFontPosBottom();
  24. u8g2.clearBuffer();
  25. u8g2.setFlipMode(FLIP_SCREEN);
  26. String s = F("OpenChrono");
  27. u8g2.setFont(HEADING_FONT);
  28. uint8_t heading_height = u8g2.getMaxCharHeight();
  29. u8g2.drawStr(
  30. 0,
  31. heading_height,
  32. s.c_str()
  33. );
  34. s = F("Version ");
  35. s += F(VERSION);
  36. u8g2.setFont(TEXT_FONT);
  37. u8g2.drawStr(
  38. (u8g2.getDisplayWidth() - u8g2.getStrWidth(s.c_str())) / 2,
  39. heading_height + u8g2.getMaxCharHeight() + 4,
  40. s.c_str()
  41. );
  42. s = String((double)SENSOR_DISTANCE, 0);
  43. s += F("mm");
  44. u8g2.setFont(TEXT_FONT);
  45. u8g2.drawStr(
  46. 0,
  47. u8g2.getDisplayHeight() - u8g2.getMaxCharHeight() - 2,
  48. s.c_str()
  49. );
  50. s = String((double)readVcc() / 1000.0, 1);
  51. s += F("V");
  52. u8g2.setFont(TEXT_FONT);
  53. u8g2.drawStr(
  54. (u8g2.getDisplayWidth() - u8g2.getStrWidth(s.c_str())) / 2,
  55. u8g2.getDisplayHeight() - u8g2.getMaxCharHeight() - 2,
  56. s.c_str()
  57. );
  58. s = String((double)BB_WEIGHT, 2);
  59. s += F("g");
  60. u8g2.setFont(TEXT_FONT);
  61. u8g2.drawStr(
  62. u8g2.getDisplayWidth() - u8g2.getStrWidth(s.c_str()),
  63. u8g2.getDisplayHeight() - u8g2.getMaxCharHeight() - 2,
  64. s.c_str()
  65. );
  66. s = F("by xythobuz.de");
  67. u8g2.setFont(TEXT_FONT);
  68. u8g2.drawStr(
  69. (u8g2.getDisplayWidth() - u8g2.getStrWidth(s.c_str())) / 2,
  70. u8g2.getDisplayHeight() - 1,
  71. s.c_str()
  72. );
  73. u8g2.sendBuffer();
  74. }
  75. void lcd_new_value(void) {
  76. lcd_rotate_time = millis();
  77. lcd_screen = 0;
  78. lcd_draw(screens[lcd_screen]);
  79. }
  80. void lcd_draw(uint8_t screen) {
  81. // fall back to first screen when no more data available
  82. if (tick_count <= 1) {
  83. screen = SCREEN_CURRENT;
  84. }
  85. if ((screen == SCREEN_CURRENT) || (screen == SCREEN_AVERAGE)
  86. || (screen == SCREEN_MIN) || (screen == SCREEN_MAX)) {
  87. if (tick_count < 1) {
  88. u8g2.clearBuffer();
  89. u8g2.setFlipMode(FLIP_SCREEN);
  90. String s = F("Ready!");
  91. u8g2.setFont(HEADING_FONT);
  92. u8g2.drawStr(
  93. (u8g2.getDisplayWidth() - u8g2.getStrWidth(s.c_str())) / 2,
  94. (u8g2.getDisplayHeight() + u8g2.getMaxCharHeight()) / 2,
  95. s.c_str()
  96. );
  97. u8g2.sendBuffer();
  98. return;
  99. }
  100. uint16_t tick = 0;
  101. if (screen == SCREEN_CURRENT) {
  102. // only show most recent value
  103. tick = tick_history[tick_count - 1];
  104. } else if (screen == SCREEN_AVERAGE) {
  105. tick = tick_average();
  106. } else if (screen == SCREEN_MAX) {
  107. tick = tick_min();
  108. } else if (screen == SCREEN_MIN) {
  109. tick = tick_max();
  110. }
  111. double metric = tick_to_metric(tick);
  112. double imperial = metric_to_imperial(metric);
  113. double weights[] = BB_WEIGHTS;
  114. double joules[sizeof(weights) / sizeof(weights[0])];
  115. for (uint8_t i = 0; i < (sizeof(weights) / sizeof(weights[0])); i++) {
  116. joules[i] = metric_to_joules(metric, weights[i]);
  117. }
  118. u8g2.clearBuffer();
  119. u8g2.setFlipMode(FLIP_SCREEN);
  120. u8g2.setFont(TEXT_FONT);
  121. String s;
  122. if (screen == SCREEN_CURRENT) {
  123. s = F("Last Shot (No. ");
  124. } else if (screen == SCREEN_AVERAGE) {
  125. s = F("Average (of ");
  126. } else if (screen == SCREEN_MAX) {
  127. s = F("Maximum (of ");
  128. } else if (screen == SCREEN_MIN) {
  129. s = F("Minimum (of ");
  130. }
  131. s += String(tick_count);
  132. s += F(")");
  133. u8g2.drawStr(
  134. (u8g2.getDisplayWidth() - u8g2.getStrWidth(s.c_str())) / 2,
  135. u8g2.getMaxCharHeight(),
  136. s.c_str()
  137. );
  138. uint8_t left_off = (u8g2.getDisplayHeight() - (u8g2.getMaxCharHeight() * 4 + 3)) / 2;
  139. if (metric < 10.0) {
  140. s = String(metric, 1);
  141. } else {
  142. s = String(metric, 0);
  143. }
  144. s += F(" m/s");
  145. uint8_t l1 = u8g2.getStrWidth(s.c_str());
  146. u8g2.drawStr(
  147. 0,
  148. u8g2.getMaxCharHeight() * 2 + 1 + left_off,
  149. s.c_str()
  150. );
  151. if (imperial < 10.0) {
  152. s = String(imperial, 1);
  153. } else {
  154. s = String(imperial, 0);
  155. }
  156. s += F(" FPS");
  157. uint8_t l2 = u8g2.getStrWidth(s.c_str());
  158. u8g2.drawStr(
  159. 0,
  160. u8g2.getMaxCharHeight() * 3 + 2 + left_off,
  161. s.c_str()
  162. );
  163. uint8_t l3 = 0;
  164. for (uint8_t i = 0; i < (sizeof(weights) / sizeof(weights[0])); i++) {
  165. if (weights[i] == BB_WEIGHT) {
  166. s = String(joules[i], 2);
  167. s += F(" J");
  168. l3 = u8g2.getStrWidth(s.c_str());
  169. u8g2.drawStr(
  170. 0,
  171. u8g2.getMaxCharHeight() * 4 + 3 + left_off,
  172. s.c_str()
  173. );
  174. break;
  175. }
  176. }
  177. uint8_t l4 = 0;
  178. uint8_t h = u8g2.getMaxCharHeight() + 1;
  179. for (uint8_t i = 0; i < (sizeof(weights) / sizeof(weights[0])); i++) {
  180. if (weights[i] == BB_WEIGHT) {
  181. //u8g2.setFont(TEXT_FONT);
  182. continue;
  183. } else {
  184. u8g2.setFont(SMALL_FONT);
  185. }
  186. s = String(weights[i], 2);
  187. s += F("g ");
  188. s += String(joules[i], 2);
  189. s += F("J");
  190. uint8_t l = u8g2.getStrWidth(s.c_str());
  191. if (l > l4) {
  192. l4 = l;
  193. }
  194. h += u8g2.getMaxCharHeight() + 1;
  195. u8g2.drawStr(
  196. u8g2.getDisplayWidth() - l,
  197. h,
  198. s.c_str()
  199. );
  200. }
  201. uint8_t l_left = max(max(l1, l2), l3);
  202. uint8_t l_right = u8g2.getDisplayWidth() - l4;
  203. uint8_t lmax = (uint8_t)((((uint16_t)l_left) + ((uint16_t)l_right)) / 2);
  204. u8g2.setFont(TEXT_FONT);
  205. u8g2.drawLine(
  206. lmax, u8g2.getMaxCharHeight() + 2,
  207. lmax, u8g2.getDisplayHeight()
  208. );
  209. u8g2.sendBuffer();
  210. } else if (screen == SCREEN_HISTORY) {
  211. uint16_t min = tick_max();
  212. uint16_t max = tick_min();
  213. String s;
  214. u8g2.clearBuffer();
  215. u8g2.setFlipMode(FLIP_SCREEN);
  216. u8g2.setFont(TEXT_FONT);
  217. // max text
  218. double max_metric = tick_to_metric(max);
  219. if (PREFERRED_UNITS == METRIC) {
  220. if (max_metric < 10.0) {
  221. s = String(max_metric, 1);
  222. } else {
  223. s = String(max_metric, 0);
  224. }
  225. } else if (PREFERRED_UNITS == IMPERIAL) {
  226. double max_imperial = metric_to_imperial(max_metric);
  227. if (max_imperial < 10.0) {
  228. s = String(max_imperial, 1);
  229. } else {
  230. s = String(max_imperial, 0);
  231. }
  232. } else {
  233. s = String(metric_to_joules(max_metric, BB_WEIGHT), 2);
  234. }
  235. uint8_t l1 = u8g2.getStrWidth(s.c_str());
  236. u8g2.drawStr(
  237. 0,
  238. u8g2.getMaxCharHeight(),
  239. s.c_str()
  240. );
  241. // unit indicator
  242. uint8_t l2;
  243. if (PREFERRED_UNITS == METRIC) {
  244. s = F("m/s");
  245. l2 = u8g2.getStrWidth(s.c_str());
  246. u8g2.drawStr(
  247. 0,
  248. (u8g2.getDisplayHeight() + u8g2.getMaxCharHeight()) / 2,
  249. s.c_str()
  250. );
  251. } else if (PREFERRED_UNITS == IMPERIAL) {
  252. s = F("FPS");
  253. l2 = u8g2.getStrWidth(s.c_str());
  254. u8g2.drawStr(
  255. 0,
  256. (u8g2.getDisplayHeight() + u8g2.getMaxCharHeight()) / 2,
  257. s.c_str()
  258. );
  259. } else {
  260. s = F("J");
  261. l2 = u8g2.getStrWidth(s.c_str());
  262. u8g2.drawStr(
  263. 0,
  264. (u8g2.getDisplayHeight() + u8g2.getMaxCharHeight()) / 2,
  265. s.c_str()
  266. );
  267. }
  268. // min text
  269. double min_metric = tick_to_metric(min);
  270. if (PREFERRED_UNITS == METRIC) {
  271. if (min_metric < 10.0) {
  272. s = String(min_metric, 1);
  273. } else {
  274. s = String(min_metric, 0);
  275. }
  276. } else if (PREFERRED_UNITS == IMPERIAL) {
  277. double min_imperial = metric_to_imperial(min_metric);
  278. if (min_imperial < 10.0) {
  279. s = String(min_imperial, 1);
  280. } else {
  281. s = String(min_imperial, 0);
  282. }
  283. } else {
  284. s = String(metric_to_joules(min_metric, BB_WEIGHT), 2);
  285. }
  286. uint8_t l3 = u8g2.getStrWidth(s.c_str());
  287. u8g2.drawStr(
  288. 0,
  289. u8g2.getDisplayHeight() - 1,
  290. s.c_str()
  291. );
  292. uint8_t lmax = max(max(l1, l2), l3);
  293. uint8_t graph_start = lmax + 1;
  294. // graph lines
  295. uint8_t segment_w = (u8g2.getDisplayWidth() - graph_start) / (tick_count - 1);
  296. for (int i = 0; i < tick_count - 1; i++) {
  297. u8g2.drawLine(
  298. graph_start + (i * segment_w),
  299. map(tick_history[i], min, max, u8g2.getDisplayHeight() - 1, 0),
  300. graph_start + ((i + 1) * segment_w),
  301. map(tick_history[i + 1], min, max, u8g2.getDisplayHeight() - 1, 0)
  302. );
  303. }
  304. u8g2.sendBuffer();
  305. }
  306. }
  307. void lcd_loop(void) {
  308. if ((millis() - lcd_rotate_time) > SCREEN_TIMEOUT) {
  309. lcd_rotate_time = millis();
  310. lcd_screen++;
  311. if (lcd_screen >= (sizeof(screens) / sizeof(screens[0]))) {
  312. lcd_screen = 0;
  313. }
  314. lcd_draw(screens[lcd_screen]);
  315. }
  316. }