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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  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(1);
  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() - 4,
  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() - 4,
  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() - 4,
  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(1);
  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 joules = metric_to_joules(metric, BB_WEIGHT);
  114. u8g2.clearBuffer();
  115. u8g2.setFlipMode(1);
  116. u8g2.setFont(TEXT_FONT);
  117. String s;
  118. if (screen == SCREEN_CURRENT) {
  119. s = F("Last Shot (No. ");
  120. } else if (screen == SCREEN_AVERAGE) {
  121. s = F("Average (of ");
  122. } else if (screen == SCREEN_MAX) {
  123. s = F("Maximum (of ");
  124. } else if (screen == SCREEN_MIN) {
  125. s = F("Minimum (of ");
  126. }
  127. s += String(tick_count);
  128. s += F(")");
  129. u8g2.drawStr(
  130. (u8g2.getDisplayWidth() - u8g2.getStrWidth(s.c_str())) / 2,
  131. u8g2.getMaxCharHeight(),
  132. s.c_str()
  133. );
  134. s = String(metric, 0);
  135. s += F(" m/s");
  136. u8g2.drawStr(
  137. 0,
  138. u8g2.getMaxCharHeight() * 2 + 1,
  139. s.c_str()
  140. );
  141. s = String(imperial, 0);
  142. s += F(" FPS");
  143. u8g2.drawStr(
  144. 0,
  145. u8g2.getMaxCharHeight() * 3 + 2,
  146. s.c_str()
  147. );
  148. s = String(joules, 2);
  149. s += F(" J");
  150. u8g2.drawStr(
  151. 0,
  152. u8g2.getMaxCharHeight() * 4 + 3,
  153. s.c_str()
  154. );
  155. u8g2.sendBuffer();
  156. } else if (screen == SCREEN_HISTORY) {
  157. uint16_t min = tick_max();
  158. uint16_t max = tick_min();
  159. String s;
  160. u8g2.clearBuffer();
  161. u8g2.setFlipMode(1);
  162. u8g2.setFont(TEXT_FONT);
  163. // max text
  164. double max_metric = tick_to_metric(max);
  165. if (PREFERRED_UNITS == METRIC) {
  166. s = String(max_metric, 0);
  167. } else if (PREFERRED_UNITS == IMPERIAL) {
  168. s = String(metric_to_imperial(max_metric), 0);
  169. } else {
  170. s = String(metric_to_joules(max_metric, BB_WEIGHT), 2);
  171. }
  172. uint8_t l1 = u8g2.getStrWidth(s.c_str());
  173. u8g2.drawStr(
  174. 0,
  175. u8g2.getMaxCharHeight(),
  176. s.c_str()
  177. );
  178. // unit indicator
  179. uint8_t l2;
  180. if (PREFERRED_UNITS == METRIC) {
  181. s = F("m/s");
  182. l2 = u8g2.getStrWidth(s.c_str());
  183. u8g2.drawStr(
  184. 0,
  185. (u8g2.getDisplayHeight() + u8g2.getMaxCharHeight()) / 2,
  186. s.c_str()
  187. );
  188. } else if (PREFERRED_UNITS == IMPERIAL) {
  189. s = F("FPS");
  190. l2 = u8g2.getStrWidth(s.c_str());
  191. u8g2.drawStr(
  192. 0,
  193. (u8g2.getDisplayHeight() + u8g2.getMaxCharHeight()) / 2,
  194. s.c_str()
  195. );
  196. } else {
  197. s = F("J");
  198. l2 = u8g2.getStrWidth(s.c_str());
  199. u8g2.drawStr(
  200. 0,
  201. (u8g2.getDisplayHeight() + u8g2.getMaxCharHeight()) / 2,
  202. s.c_str()
  203. );
  204. }
  205. // min text
  206. double min_metric = tick_to_metric(min);
  207. if (PREFERRED_UNITS == METRIC) {
  208. s = String(min_metric, 0);
  209. } else if (PREFERRED_UNITS == IMPERIAL) {
  210. s = String(metric_to_imperial(min_metric), 0);
  211. } else {
  212. s = String(metric_to_joules(min_metric, BB_WEIGHT), 2);
  213. }
  214. uint8_t l3 = u8g2.getStrWidth(s.c_str());
  215. u8g2.drawStr(
  216. 0,
  217. u8g2.getDisplayHeight() - 1,
  218. s.c_str()
  219. );
  220. uint8_t lmax = max(max(l1, l2), l3);
  221. uint8_t graph_start = lmax + 1;
  222. // graph lines
  223. uint8_t segment_w = (u8g2.getDisplayWidth() - graph_start) / (tick_count - 1);
  224. for (int i = 0; i < tick_count - 1; i++) {
  225. u8g2.drawLine(
  226. graph_start + (i * segment_w),
  227. map(tick_history[i], min, max, 0, u8g2.getDisplayHeight() - 1),
  228. graph_start + ((i + 1) * segment_w),
  229. map(tick_history[i + 1], min, max, 0, u8g2.getDisplayHeight() - 1)
  230. );
  231. }
  232. u8g2.sendBuffer();
  233. }
  234. }
  235. void lcd_loop(void) {
  236. if ((millis() - lcd_rotate_time) > SCREEN_TIMEOUT) {
  237. lcd_rotate_time = millis();
  238. lcd_screen++;
  239. if (lcd_screen >= sizeof(screens)) {
  240. lcd_screen = 0;
  241. }
  242. lcd_draw(screens[lcd_screen]);
  243. }
  244. }