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 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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. s = String(metric, 0);
  140. s += F(" m/s");
  141. uint8_t l1 = u8g2.getStrWidth(s.c_str());
  142. u8g2.drawStr(
  143. 0,
  144. u8g2.getMaxCharHeight() * 2 + 1 + left_off,
  145. s.c_str()
  146. );
  147. s = String(imperial, 0);
  148. s += F(" FPS");
  149. uint8_t l2 = u8g2.getStrWidth(s.c_str());
  150. u8g2.drawStr(
  151. 0,
  152. u8g2.getMaxCharHeight() * 3 + 2 + left_off,
  153. s.c_str()
  154. );
  155. uint8_t l3 = 0;
  156. for (uint8_t i = 0; i < (sizeof(weights) / sizeof(weights[0])); i++) {
  157. if (weights[i] == BB_WEIGHT) {
  158. s = String(joules[i], 2);
  159. s += F(" J");
  160. l3 = u8g2.getStrWidth(s.c_str());
  161. u8g2.drawStr(
  162. 0,
  163. u8g2.getMaxCharHeight() * 4 + 3 + left_off,
  164. s.c_str()
  165. );
  166. break;
  167. }
  168. }
  169. uint8_t l4 = 0;
  170. uint8_t h = u8g2.getMaxCharHeight() + 1;
  171. for (uint8_t i = 0; i < (sizeof(weights) / sizeof(weights[0])); i++) {
  172. if (weights[i] == BB_WEIGHT) {
  173. //u8g2.setFont(TEXT_FONT);
  174. continue;
  175. } else {
  176. u8g2.setFont(SMALL_FONT);
  177. }
  178. s = String(weights[i], 2);
  179. s += F("g ");
  180. s += String(joules[i], 2);
  181. s += F("J");
  182. uint8_t l = u8g2.getStrWidth(s.c_str());
  183. if (l > l4) {
  184. l4 = l;
  185. }
  186. h += u8g2.getMaxCharHeight() + 1;
  187. u8g2.drawStr(
  188. u8g2.getDisplayWidth() - l,
  189. h,
  190. s.c_str()
  191. );
  192. }
  193. uint8_t l_left = max(max(l1, l2), l3);
  194. uint8_t l_right = u8g2.getDisplayWidth() - l4;
  195. uint8_t lmax = (uint8_t)((((uint16_t)l_left) + ((uint16_t)l_right)) / 2);
  196. u8g2.setFont(TEXT_FONT);
  197. u8g2.drawLine(
  198. lmax, u8g2.getMaxCharHeight() + 2,
  199. lmax, u8g2.getDisplayHeight()
  200. );
  201. u8g2.sendBuffer();
  202. } else if (screen == SCREEN_HISTORY) {
  203. uint16_t min = tick_max();
  204. uint16_t max = tick_min();
  205. String s;
  206. u8g2.clearBuffer();
  207. u8g2.setFlipMode(FLIP_SCREEN);
  208. u8g2.setFont(TEXT_FONT);
  209. // max text
  210. double max_metric = tick_to_metric(max);
  211. if (PREFERRED_UNITS == METRIC) {
  212. s = String(max_metric, 0);
  213. } else if (PREFERRED_UNITS == IMPERIAL) {
  214. s = String(metric_to_imperial(max_metric), 0);
  215. } else {
  216. s = String(metric_to_joules(max_metric, BB_WEIGHT), 2);
  217. }
  218. uint8_t l1 = u8g2.getStrWidth(s.c_str());
  219. u8g2.drawStr(
  220. 0,
  221. u8g2.getMaxCharHeight(),
  222. s.c_str()
  223. );
  224. // unit indicator
  225. uint8_t l2;
  226. if (PREFERRED_UNITS == METRIC) {
  227. s = F("m/s");
  228. l2 = u8g2.getStrWidth(s.c_str());
  229. u8g2.drawStr(
  230. 0,
  231. (u8g2.getDisplayHeight() + u8g2.getMaxCharHeight()) / 2,
  232. s.c_str()
  233. );
  234. } else if (PREFERRED_UNITS == IMPERIAL) {
  235. s = F("FPS");
  236. l2 = u8g2.getStrWidth(s.c_str());
  237. u8g2.drawStr(
  238. 0,
  239. (u8g2.getDisplayHeight() + u8g2.getMaxCharHeight()) / 2,
  240. s.c_str()
  241. );
  242. } else {
  243. s = F("J");
  244. l2 = u8g2.getStrWidth(s.c_str());
  245. u8g2.drawStr(
  246. 0,
  247. (u8g2.getDisplayHeight() + u8g2.getMaxCharHeight()) / 2,
  248. s.c_str()
  249. );
  250. }
  251. // min text
  252. double min_metric = tick_to_metric(min);
  253. if (PREFERRED_UNITS == METRIC) {
  254. s = String(min_metric, 0);
  255. } else if (PREFERRED_UNITS == IMPERIAL) {
  256. s = String(metric_to_imperial(min_metric), 0);
  257. } else {
  258. s = String(metric_to_joules(min_metric, BB_WEIGHT), 2);
  259. }
  260. uint8_t l3 = u8g2.getStrWidth(s.c_str());
  261. u8g2.drawStr(
  262. 0,
  263. u8g2.getDisplayHeight() - 1,
  264. s.c_str()
  265. );
  266. uint8_t lmax = max(max(l1, l2), l3);
  267. uint8_t graph_start = lmax + 1;
  268. // graph lines
  269. uint8_t segment_w = (u8g2.getDisplayWidth() - graph_start) / (tick_count - 1);
  270. for (int i = 0; i < tick_count - 1; i++) {
  271. u8g2.drawLine(
  272. graph_start + (i * segment_w),
  273. map(tick_history[i], min, max, 0, u8g2.getDisplayHeight() - 1),
  274. graph_start + ((i + 1) * segment_w),
  275. map(tick_history[i + 1], min, max, 0, u8g2.getDisplayHeight() - 1)
  276. );
  277. }
  278. u8g2.sendBuffer();
  279. }
  280. }
  281. void lcd_loop(void) {
  282. if ((millis() - lcd_rotate_time) > SCREEN_TIMEOUT) {
  283. lcd_rotate_time = millis();
  284. lcd_screen++;
  285. if (lcd_screen >= (sizeof(screens) / sizeof(screens[0]))) {
  286. lcd_screen = 0;
  287. }
  288. lcd_draw(screens[lcd_screen]);
  289. }
  290. }