display_task.cpp 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. #include "display_task.h"
  2. #include "semaphore_guard.h"
  3. #include "font/roboto_light_60.h"
  4. DisplayTask::DisplayTask(const uint8_t task_core) : Task{"Display", 2048, 1, task_core} {
  5. semaphore_ = xSemaphoreCreateMutex();
  6. assert(semaphore_ != NULL);
  7. xSemaphoreGive(semaphore_);
  8. }
  9. DisplayTask::~DisplayTask() {
  10. if (semaphore_ != NULL) {
  11. vSemaphoreDelete(semaphore_);
  12. }
  13. }
  14. static void HSV_to_RGB(float h, float s, float v, uint8_t *r, uint8_t *g, uint8_t *b)
  15. {
  16. int i;
  17. float f,p,q,t;
  18. h = fmax(0.0, fmin(360.0, h));
  19. s = fmax(0.0, fmin(100.0, s));
  20. v = fmax(0.0, fmin(100.0, v));
  21. s /= 100;
  22. v /= 100;
  23. if(s == 0) {
  24. // Achromatic (grey)
  25. *r = *g = *b = round(v*255);
  26. return;
  27. }
  28. h /= 60; // sector 0 to 5
  29. i = floor(h);
  30. f = h - i; // factorial part of h
  31. p = v * (1 - s);
  32. q = v * (1 - s * f);
  33. t = v * (1 - s * (1 - f));
  34. switch(i) {
  35. case 0:
  36. *r = round(255*v);
  37. *g = round(255*t);
  38. *b = round(255*p);
  39. break;
  40. case 1:
  41. *r = round(255*q);
  42. *g = round(255*v);
  43. *b = round(255*p);
  44. break;
  45. case 2:
  46. *r = round(255*p);
  47. *g = round(255*v);
  48. *b = round(255*t);
  49. break;
  50. case 3:
  51. *r = round(255*p);
  52. *g = round(255*q);
  53. *b = round(255*v);
  54. break;
  55. case 4:
  56. *r = round(255*t);
  57. *g = round(255*p);
  58. *b = round(255*v);
  59. break;
  60. default: // case 5:
  61. *r = round(255*v);
  62. *g = round(255*p);
  63. *b = round(255*q);
  64. }
  65. }
  66. void DisplayTask::run() {
  67. delay(100);
  68. tft_.begin();
  69. tft_.invertDisplay(1);
  70. tft_.setRotation(0);
  71. tft_.fillScreen(TFT_PURPLE);
  72. spr_.setColorDepth(16);
  73. if (spr_.createSprite(TFT_WIDTH, TFT_HEIGHT) == nullptr) {
  74. Serial.println("ERROR: sprite allocation failed!");
  75. tft_.fillScreen(TFT_RED);
  76. }
  77. spr_.setTextColor(0xFFFF, TFT_BLACK);
  78. KnobState state;
  79. const int RADIUS = TFT_WIDTH / 2;
  80. const uint16_t FILL_COLOR = spr_.color565(90, 18, 151);
  81. const uint16_t DOT_COLOR = spr_.color565(80, 100, 200);
  82. int32_t pointer_center_x = TFT_WIDTH / 2;
  83. int32_t pointer_center_y = TFT_HEIGHT / 2;
  84. int32_t pointer_length_short = 10;
  85. int32_t pointer_length_long = TFT_WIDTH / 2 - 5;
  86. spr_.setTextDatum(CC_DATUM);
  87. spr_.setTextColor(TFT_WHITE);
  88. while(1) {
  89. {
  90. SemaphoreGuard lock(semaphore_);
  91. state = state_;
  92. }
  93. spr_.fillSprite(TFT_BLACK);
  94. if (state.config.num_positions > 1) {
  95. int32_t height = state.current_position * TFT_HEIGHT / (state.config.num_positions - 1);
  96. spr_.fillRect(0, TFT_HEIGHT - height, TFT_WIDTH, height, FILL_COLOR);
  97. }
  98. spr_.setFreeFont(&Roboto_Light_60);
  99. spr_.drawString(String() + state.current_position, TFT_WIDTH / 2, TFT_HEIGHT / 2 - 30, 1);
  100. spr_.setFreeFont(&Roboto_Thin_24);
  101. int32_t line_y = TFT_HEIGHT / 2 + 20;
  102. char* start = state.config.descriptor;
  103. char* end = start + strlen(state.config.descriptor);
  104. while (start < end) {
  105. char* newline = strchr(start, '\n');
  106. if (newline == nullptr) {
  107. newline = end;
  108. }
  109. char buf[sizeof(state.config.descriptor)] = {};
  110. strncat(buf, start, min(sizeof(buf) - 1, (size_t)(newline - start)));
  111. spr_.drawString(String(buf), TFT_WIDTH / 2, line_y, 1);
  112. start = newline + 1;
  113. line_y += spr_.fontHeight(1);
  114. }
  115. float left_bound = PI / 2;
  116. if (state.config.num_positions > 0) {
  117. float range_radians = (state.config.num_positions - 1) * state.config.position_width_radians;
  118. left_bound = PI / 2 + range_radians / 2;
  119. float right_bound = PI / 2 - range_radians / 2;
  120. spr_.drawLine(TFT_WIDTH/2 + RADIUS * cosf(left_bound), TFT_HEIGHT/2 - RADIUS * sinf(left_bound), TFT_WIDTH/2 + (RADIUS - 10) * cosf(left_bound), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(left_bound), TFT_WHITE);
  121. spr_.drawLine(TFT_WIDTH/2 + RADIUS * cosf(right_bound), TFT_HEIGHT/2 - RADIUS * sinf(right_bound), TFT_WIDTH/2 + (RADIUS - 10) * cosf(right_bound), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(right_bound), TFT_WHITE);
  122. }
  123. float adjusted_sub_position = state.sub_position_unit * state.config.position_width_radians;
  124. if (state.config.num_positions > 0) {
  125. if (state.current_position == 0 && state.sub_position_unit < 0) {
  126. adjusted_sub_position = -logf(1 - state.sub_position_unit * state.config.position_width_radians / 5 / PI * 180) * 5 * PI / 180;
  127. } else if (state.current_position == state.config.num_positions - 1 && state.sub_position_unit > 0) {
  128. adjusted_sub_position = logf(1 + state.sub_position_unit * state.config.position_width_radians / 5 / PI * 180) * 5 * PI / 180;
  129. }
  130. }
  131. float raw_angle = left_bound - state.current_position * state.config.position_width_radians;
  132. float adjusted_angle = raw_angle - adjusted_sub_position;
  133. if (state.config.num_positions > 0 && ((state.current_position == 0 && state.sub_position_unit < 0) || (state.current_position == state.config.num_positions - 1 && state.sub_position_unit > 0))) {
  134. spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(raw_angle), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(raw_angle), 5, DOT_COLOR);
  135. if (raw_angle < adjusted_angle) {
  136. for (float r = raw_angle; r <= adjusted_angle; r += 2 * PI / 180) {
  137. spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(r), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(r), 2, DOT_COLOR);
  138. }
  139. spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(adjusted_angle), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(adjusted_angle), 2, DOT_COLOR);
  140. } else {
  141. for (float r = raw_angle; r >= adjusted_angle; r -= 2 * PI / 180) {
  142. spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(r), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(r), 2, DOT_COLOR);
  143. }
  144. spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(adjusted_angle), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(adjusted_angle), 2, DOT_COLOR);
  145. }
  146. } else {
  147. spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(adjusted_angle), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(adjusted_angle), 5, DOT_COLOR);
  148. }
  149. spr_.pushSprite(0, 0);
  150. delay(2);
  151. }
  152. }
  153. void DisplayTask::setData(KnobState state) {
  154. SemaphoreGuard lock(semaphore_);
  155. state_ = state;
  156. }