app.c 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. #include <furi.h>
  2. #include <furi_hal.h>
  3. #include <furi_hal_pwm.h>
  4. #include <input/input.h>
  5. #include <gui/gui.h>
  6. #include <stdlib.h>
  7. #include <stm32wbxx_ll_tim.h>
  8. #include <stm32wbxx_ll_lptim.h>
  9. #include <stm32wbxx_ll_rcc.h>
  10. // ./fbt.cmd launch_app APPSRC=servotester
  11. #define SCREEN_XRES 128
  12. #define SCREEN_YRES 64
  13. #define MIN_ANGLE 0
  14. #define MAX_ANGLE 180
  15. #define FREQ 50
  16. typedef enum Mode
  17. {
  18. Manual = 1,
  19. Center = 2,
  20. Sweep = 3,
  21. } Mode;
  22. typedef struct App
  23. {
  24. Gui *gui;
  25. ViewPort *view_port;
  26. FuriMessageQueue *event_queue;
  27. int running;
  28. Mode mode;
  29. uint8_t angle;
  30. FuriHalPwmOutputId ch_prev;
  31. FuriHalPwmOutputId ch;
  32. } App;
  33. void input_callback(InputEvent *input_event, void *ctx)
  34. {
  35. App *app = ctx;
  36. furi_message_queue_put(app->event_queue, input_event, FuriWaitForever);
  37. }
  38. uint32_t angle_to_compare(uint8_t angle)
  39. {
  40. /*
  41. 1%: freq_div: 1280000, prescaler: 19, period: 64000, compare: 640
  42. 3%: freq_div: 1280000, prescaler: 19, period: 64000, compare: 1920
  43. 5%: freq_div: 1280000, prescaler: 19, period: 64000, compare: 3200
  44. 7.5%: freq_div: 1280000, prescaler: 19, period: 64000, compare: 4800
  45. 7%: freq_div: 1280000, prescaler: 19, period: 64000, compare: 4480
  46. 10%: freq_div: 1280000, prescaler: 19, period: 64000, compare: 6400
  47. 13%: freq_div: 1280000, prescaler: 19, period: 64000, compare: 8320
  48. 15%: freq_div: 1280000, prescaler: 19, period: 64000, compare: 9600
  49. 20%: freq_div: 1280000, prescaler: 19, period: 64000, compare: 12800
  50. */
  51. uint32_t min_compare = 1920;
  52. uint32_t max_compare = 8320;
  53. if (angle == MIN_ANGLE)
  54. return min_compare;
  55. if (angle == MAX_ANGLE)
  56. return max_compare;
  57. return min_compare + floor(((float)angle / (float)MAX_ANGLE) * (max_compare - min_compare));
  58. }
  59. void render_callback(Canvas *const canvas, void *ctx)
  60. {
  61. App *app = ctx;
  62. canvas_set_color(canvas, ColorWhite);
  63. canvas_draw_box(canvas, 0, 0, SCREEN_XRES - 1, SCREEN_YRES - 1);
  64. canvas_set_color(canvas, ColorBlack);
  65. canvas_set_font(canvas, FontSecondary);
  66. uint8_t mode_base_x = 20;
  67. canvas_draw_str(canvas, mode_base_x, 10, "Mode");
  68. canvas_draw_str(canvas, mode_base_x, 30, "Manual");
  69. canvas_draw_str(canvas, mode_base_x, 45, "Center");
  70. canvas_draw_str(canvas, mode_base_x, 60, "Sweep");
  71. uint8_t selector_base_x = 5;
  72. uint8_t r = 4;
  73. uint8_t correction = r;
  74. if (app->mode == Manual)
  75. {
  76. canvas_draw_disc(canvas, selector_base_x, 30 - correction, r);
  77. }
  78. else
  79. {
  80. canvas_draw_circle(canvas, selector_base_x, 30 - correction, r);
  81. }
  82. if (app->mode == Center)
  83. {
  84. canvas_draw_disc(canvas, selector_base_x, 45 - correction, r);
  85. }
  86. else
  87. {
  88. canvas_draw_circle(canvas, selector_base_x, 45 - correction, r);
  89. }
  90. if (app->mode == Sweep)
  91. {
  92. canvas_draw_disc(canvas, selector_base_x, 60 - correction, r);
  93. }
  94. else
  95. {
  96. canvas_draw_circle(canvas, selector_base_x, 60 - correction, r);
  97. }
  98. uint8_t angle_base_x = 70;
  99. canvas_draw_str(canvas, angle_base_x, 10, "Angle");
  100. canvas_draw_circle(canvas, angle_base_x + 18, 21, 2);
  101. char angle_str[32];
  102. snprintf(angle_str, sizeof(angle_str), "%3u", app->angle);
  103. canvas_draw_str(canvas, angle_base_x, 30, angle_str);
  104. }
  105. App *app_alloc()
  106. {
  107. App *app = malloc(sizeof(App));
  108. app->gui = furi_record_open(RECORD_GUI);
  109. app->view_port = view_port_alloc();
  110. view_port_draw_callback_set(app->view_port, render_callback, app);
  111. view_port_input_callback_set(app->view_port, input_callback, app);
  112. gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
  113. app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
  114. app->running = 1;
  115. app->mode = Manual;
  116. app->ch = FuriHalPwmOutputIdTim1PA7; // A7
  117. app->angle = 0;
  118. return app;
  119. }
  120. void app_free(App *app)
  121. {
  122. furi_assert(app);
  123. furi_hal_pwm_stop(app->ch);
  124. view_port_enabled_set(app->view_port, false);
  125. gui_remove_view_port(app->gui, app->view_port);
  126. view_port_free(app->view_port);
  127. furi_record_close(RECORD_GUI);
  128. furi_message_queue_free(app->event_queue);
  129. app->gui = NULL;
  130. free(app);
  131. }
  132. void update_state(App *app, InputKey key)
  133. {
  134. if (key == InputKeyUp)
  135. {
  136. if (app->mode == Manual)
  137. {
  138. app->angle = MIN_ANGLE;
  139. app->mode = Sweep;
  140. }
  141. else if (app->mode == Center)
  142. {
  143. app->mode = Manual;
  144. }
  145. else if (app->mode == Sweep)
  146. {
  147. app->mode = Center;
  148. }
  149. }
  150. if (key == InputKeyDown)
  151. {
  152. if (app->mode == Manual)
  153. {
  154. app->mode = Center;
  155. }
  156. else if (app->mode == Center)
  157. {
  158. app->angle = MIN_ANGLE;
  159. app->mode = Sweep;
  160. }
  161. else if (app->mode == Sweep)
  162. {
  163. app->mode = Manual;
  164. }
  165. }
  166. if (app->mode == Manual)
  167. {
  168. if (key == InputKeyRight)
  169. {
  170. if (app->angle < MAX_ANGLE)
  171. {
  172. app->angle = app->angle + 10;
  173. }
  174. }
  175. if (key == InputKeyLeft)
  176. {
  177. if (app->angle > MIN_ANGLE)
  178. {
  179. app->angle = app->angle - 10;
  180. }
  181. }
  182. }
  183. }
  184. void custom_pwm_set_params(uint32_t freq, uint32_t compare)
  185. {
  186. furi_assert(freq > 0);
  187. uint32_t freq_div = 64000000LU / freq;
  188. uint32_t prescaler = freq_div / 0x10000LU;
  189. uint32_t period = freq_div / (prescaler + 1);
  190. // uint32_t compare = period * duty / 100;
  191. LL_TIM_SetPrescaler(TIM1, prescaler);
  192. LL_TIM_SetAutoReload(TIM1, period - 1);
  193. LL_TIM_OC_SetCompareCH1(TIM1, compare);
  194. }
  195. void tick(void *ctx)
  196. {
  197. App *app = ctx;
  198. if (!app->running)
  199. return;
  200. if (app->mode != Sweep)
  201. return;
  202. if (app->angle == MAX_ANGLE)
  203. {
  204. app->angle = MIN_ANGLE;
  205. }
  206. else if (app->angle == MIN_ANGLE)
  207. {
  208. app->angle = MAX_ANGLE;
  209. }
  210. custom_pwm_set_params(FREQ, angle_to_compare(app->angle));
  211. }
  212. int32_t servotester_app_entry(void *p)
  213. {
  214. UNUSED(p);
  215. App *app = app_alloc();
  216. furi_hal_pwm_start(app->ch, FREQ, angle_to_compare(app->angle));
  217. custom_pwm_set_params(FREQ, angle_to_compare(app->angle));
  218. FuriTimer *timer = furi_timer_alloc(tick, FuriTimerTypePeriodic, app);
  219. furi_timer_start(timer, furi_kernel_get_tick_frequency() / 1);
  220. InputEvent input;
  221. while (app->running)
  222. {
  223. FuriStatus qstat = furi_message_queue_get(app->event_queue, &input, 100);
  224. if (qstat == FuriStatusOk)
  225. {
  226. if (input.key == InputKeyBack)
  227. {
  228. app->running = 0;
  229. // TODO: handle InputTypeLong
  230. }
  231. else if (input.type == InputTypePress)
  232. {
  233. update_state(app, input.key);
  234. if (!app->running)
  235. {
  236. break;
  237. }
  238. switch (app->mode)
  239. {
  240. case Manual:
  241. custom_pwm_set_params(FREQ, angle_to_compare(app->angle));
  242. break;
  243. case Center:
  244. app->angle = 90;
  245. custom_pwm_set_params(FREQ, angle_to_compare(app->angle));
  246. break;
  247. case Sweep:
  248. // handled in ticks
  249. break;
  250. }
  251. }
  252. }
  253. }
  254. furi_timer_free(timer);
  255. app_free(app);
  256. return 0;
  257. }