display_task.cpp 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. #if SK_DISPLAY
  2. #include "display_task.h"
  3. #include "semaphore_guard.h"
  4. #include "util.h"
  5. #include "font/roboto_light_60.h"
  6. static const uint8_t LEDC_CHANNEL_LCD_BACKLIGHT = 0;
  7. DisplayTask::DisplayTask(const uint8_t task_core) : Task{"Display", 2048, 1, task_core} {
  8. knob_state_queue_ = xQueueCreate(1, sizeof(PB_SmartKnobState));
  9. assert(knob_state_queue_ != NULL);
  10. mutex_ = xSemaphoreCreateMutex();
  11. assert(mutex_ != NULL);
  12. }
  13. DisplayTask::~DisplayTask() {
  14. vQueueDelete(knob_state_queue_);
  15. vSemaphoreDelete(mutex_);
  16. }
  17. static void drawPlayButton(TFT_eSprite& spr, int x, int y, int width, int height, uint16_t color) {
  18. spr.fillTriangle(
  19. x, y - height / 2,
  20. x, y + height / 2,
  21. x + width, y,
  22. color
  23. );
  24. }
  25. void DisplayTask::run() {
  26. tft_.begin();
  27. tft_.invertDisplay(1);
  28. tft_.setRotation(SK_DISPLAY_ROTATION);
  29. tft_.fillScreen(TFT_DARKGREEN);
  30. ledcSetup(LEDC_CHANNEL_LCD_BACKLIGHT, 5000, 16);
  31. ledcAttachPin(PIN_LCD_BACKLIGHT, LEDC_CHANNEL_LCD_BACKLIGHT);
  32. ledcWrite(LEDC_CHANNEL_LCD_BACKLIGHT, UINT16_MAX);
  33. spr_.setColorDepth(8);
  34. if (spr_.createSprite(TFT_WIDTH, TFT_HEIGHT) == nullptr) {
  35. log("ERROR: sprite allocation failed!");
  36. tft_.fillScreen(TFT_RED);
  37. } else {
  38. log("Sprite created!");
  39. tft_.fillScreen(TFT_PURPLE);
  40. }
  41. spr_.setTextColor(0xFFFF, TFT_BLACK);
  42. PB_SmartKnobState state;
  43. const int RADIUS = TFT_WIDTH / 2;
  44. const uint16_t FILL_COLOR = spr_.color565(90, 18, 151);
  45. const uint16_t DOT_COLOR = spr_.color565(80, 100, 200);
  46. spr_.setTextDatum(CC_DATUM);
  47. spr_.setTextColor(TFT_WHITE);
  48. while(1) {
  49. if (xQueueReceive(knob_state_queue_, &state, portMAX_DELAY) == pdFALSE) {
  50. continue;
  51. }
  52. spr_.fillSprite(TFT_BLACK);
  53. int32_t num_positions = state.config.max_position - state.config.min_position + 1;
  54. float adjusted_sub_position = state.sub_position_unit * state.config.position_width_radians;
  55. if (num_positions > 0) {
  56. if (state.current_position == state.config.min_position && state.sub_position_unit < 0) {
  57. adjusted_sub_position = -logf(1 - state.sub_position_unit * state.config.position_width_radians / 5 / PI * 180) * 5 * PI / 180;
  58. } else if (state.current_position == state.config.max_position && state.sub_position_unit > 0) {
  59. adjusted_sub_position = logf(1 + state.sub_position_unit * state.config.position_width_radians / 5 / PI * 180) * 5 * PI / 180;
  60. }
  61. }
  62. float left_bound = PI / 2;
  63. float right_bound = 0;
  64. if (num_positions > 0) {
  65. float range_radians = (state.config.max_position - state.config.min_position) * state.config.position_width_radians;
  66. left_bound = PI / 2 + range_radians / 2;
  67. right_bound = PI / 2 - range_radians / 2;
  68. }
  69. float raw_angle = left_bound - (state.current_position - state.config.min_position) * state.config.position_width_radians;
  70. float adjusted_angle = raw_angle - adjusted_sub_position;
  71. bool sk_demo_mode = strncmp(state.config.text, "SKDEMO_", 7) == 0;
  72. if (!sk_demo_mode) {
  73. if (num_positions > 1) {
  74. int32_t height = (state.current_position - state.config.min_position) * TFT_HEIGHT / (state.config.max_position - state.config.min_position);
  75. spr_.fillRect(0, TFT_HEIGHT - height, TFT_WIDTH, height, FILL_COLOR);
  76. }
  77. spr_.setFreeFont(&Roboto_Light_60);
  78. spr_.drawNumber(state.current_position, TFT_WIDTH / 2, TFT_HEIGHT / 2 - VALUE_OFFSET, 1);
  79. spr_.setFreeFont(&DESCRIPTION_FONT);
  80. int32_t line_y = TFT_HEIGHT / 2 + DESCRIPTION_Y_OFFSET;
  81. char* start = state.config.text;
  82. char* end = start + strlen(state.config.text);
  83. while (start < end) {
  84. char* newline = strchr(start, '\n');
  85. if (newline == nullptr) {
  86. newline = end;
  87. }
  88. char buf[sizeof(state.config.text)] = {};
  89. strncat(buf, start, min(sizeof(buf) - 1, (size_t)(newline - start)));
  90. spr_.drawString(String(buf), TFT_WIDTH / 2, line_y, 1);
  91. start = newline + 1;
  92. line_y += spr_.fontHeight(1);
  93. }
  94. if (num_positions > 0) {
  95. 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);
  96. 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);
  97. }
  98. if (DRAW_ARC) {
  99. spr_.drawCircle(TFT_WIDTH/2, TFT_HEIGHT/2, RADIUS, TFT_DARKGREY);
  100. }
  101. if (num_positions > 0 && ((state.current_position == state.config.min_position && state.sub_position_unit < 0) || (state.current_position == state.config.max_position && state.sub_position_unit > 0))) {
  102. spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(raw_angle), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(raw_angle), 5, DOT_COLOR);
  103. if (raw_angle < adjusted_angle) {
  104. for (float r = raw_angle; r <= adjusted_angle; r += 2 * PI / 180) {
  105. spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(r), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(r), 2, DOT_COLOR);
  106. }
  107. spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(adjusted_angle), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(adjusted_angle), 2, DOT_COLOR);
  108. } else {
  109. for (float r = raw_angle; r >= adjusted_angle; r -= 2 * PI / 180) {
  110. spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(r), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(r), 2, DOT_COLOR);
  111. }
  112. spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(adjusted_angle), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(adjusted_angle), 2, DOT_COLOR);
  113. }
  114. } else {
  115. spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(adjusted_angle), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(adjusted_angle), 5, DOT_COLOR);
  116. }
  117. } else {
  118. if (strncmp(state.config.text, "SKDEMO_Scroll", 13) == 0) {
  119. spr_.fillRect(0, 0, TFT_WIDTH, TFT_HEIGHT, spr_.color565(150, 0, 0));
  120. spr_.setFreeFont(&Roboto_Thin_24);
  121. spr_.drawString("Scroll", TFT_WIDTH / 2, TFT_HEIGHT / 2, 1);
  122. bool detent = false;
  123. for (uint8_t i = 0; i < state.config.detent_positions_count; i++) {
  124. if (state.config.detent_positions[i] == state.current_position) {
  125. detent = true;
  126. break;
  127. }
  128. }
  129. spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 16) * cosf(adjusted_angle), TFT_HEIGHT/2 - (RADIUS - 16) * sinf(adjusted_angle), detent ? 8 : 5, TFT_WHITE);
  130. } else if (strncmp(state.config.text, "SKDEMO_Frames", 13) == 0) {
  131. int32_t width = (state.current_position - state.config.min_position) * TFT_WIDTH / (state.config.max_position - state.config.min_position);
  132. spr_.fillRect(0, 0, width, TFT_HEIGHT, spr_.color565(0, 150, 0));
  133. spr_.setFreeFont(&Roboto_Light_60);
  134. spr_.drawNumber(state.current_position, TFT_WIDTH / 2, TFT_HEIGHT / 2, 1);
  135. spr_.setFreeFont(&Roboto_Thin_24);
  136. spr_.drawString("Frame", TFT_WIDTH / 2, TFT_HEIGHT / 2 - DESCRIPTION_Y_OFFSET - VALUE_OFFSET, 1);
  137. } else if (strncmp(state.config.text, "SKDEMO_Speed", 12) == 0) {
  138. spr_.fillRect(0, 0, TFT_WIDTH, TFT_HEIGHT, spr_.color565(0, 0, 150));
  139. float normalizedFractional = sgn(state.sub_position_unit) *
  140. CLAMP(lerp(state.sub_position_unit * sgn(state.sub_position_unit), 0.1, 0.9, 0, 1), (float)0, (float)1);
  141. float normalized = state.current_position + normalizedFractional;
  142. float speed = sgn(normalized) * powf(2, fabsf(normalized) - 1);
  143. float roundedSpeed = truncf(speed * 10) / 10;
  144. spr_.setFreeFont(&Roboto_Thin_24);
  145. if (roundedSpeed == 0) {
  146. spr_.drawString("Paused", TFT_WIDTH / 2, TFT_HEIGHT / 2 + DESCRIPTION_Y_OFFSET + VALUE_OFFSET, 1);
  147. spr_.fillRect(TFT_WIDTH / 2 + 5, TFT_HEIGHT / 2 - 20, 10, 40, TFT_WHITE);
  148. spr_.fillRect(TFT_WIDTH / 2 - 5 - 10, TFT_HEIGHT / 2 - 20, 10, 40, TFT_WHITE);
  149. } else {
  150. char buf[10];
  151. snprintf(buf, sizeof(buf), "%0.1fx", roundedSpeed);
  152. spr_.drawString(buf, TFT_WIDTH / 2, TFT_HEIGHT / 2 + DESCRIPTION_Y_OFFSET + VALUE_OFFSET, 1);
  153. uint16_t x = TFT_WIDTH / 2;
  154. for (uint8_t i = 0; i < max(1, abs(state.current_position)); i++) {
  155. drawPlayButton(spr_, x, TFT_HEIGHT / 2, sgn(roundedSpeed) * 20, 40, TFT_WHITE);
  156. x += sgn(roundedSpeed) * 20;
  157. }
  158. }
  159. }
  160. }
  161. spr_.pushSprite(0, 0);
  162. {
  163. SemaphoreGuard lock(mutex_);
  164. ledcWrite(LEDC_CHANNEL_LCD_BACKLIGHT, brightness_);
  165. }
  166. delay(5);
  167. }
  168. }
  169. QueueHandle_t DisplayTask::getKnobStateQueue() {
  170. return knob_state_queue_;
  171. }
  172. void DisplayTask::setBrightness(uint16_t brightness) {
  173. SemaphoreGuard lock(mutex_);
  174. brightness_ = brightness;
  175. }
  176. void DisplayTask::setLogger(Logger* logger) {
  177. logger_ = logger;
  178. }
  179. void DisplayTask::log(const char* msg) {
  180. if (logger_ != nullptr) {
  181. logger_->log(msg);
  182. }
  183. }
  184. #endif