view_direct_sampling.c 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. /* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
  2. * See the LICENSE file for information about the license. */
  3. #include "app.h"
  4. #include <cc1101_regs.h>
  5. static void direct_sampling_timer_start(ProtoViewApp* app);
  6. static void direct_sampling_timer_stop(ProtoViewApp* app);
  7. #define CAPTURED_BITMAP_BITS (128 * 64)
  8. #define CAPTURED_BITMAP_BYTES (CAPTURED_BITMAP_BITS / 8)
  9. #define DEFAULT_USEC_PER_PIXEL 50
  10. #define USEC_PER_PIXEL_SMALL_CHANGE 5
  11. #define USEC_PER_PIXEL_LARGE_CHANGE 25
  12. #define USEC_PER_PIXEL_MIN 5
  13. #define USEC_PER_PIXEL_MAX 300
  14. typedef struct {
  15. uint8_t* captured; // Bitmap with the last captured screen.
  16. uint32_t captured_idx; // Current index to write into the bitmap
  17. uint32_t usec_per_pixel; // Number of useconds a pixel should represent
  18. bool show_usage_info;
  19. } DirectSamplingViewPrivData;
  20. /* Read directly from the G0 CC1101 pin, and draw a black or white
  21. * dot depending on the level. */
  22. void render_view_direct_sampling(Canvas* const canvas, ProtoViewApp* app) {
  23. DirectSamplingViewPrivData* privdata = app->view_privdata;
  24. if(!app->direct_sampling_enabled && privdata->show_usage_info) {
  25. canvas_set_font(canvas, FontSecondary);
  26. canvas_draw_str(canvas, 2, 9, "Direct sampling displays the");
  27. canvas_draw_str(canvas, 2, 18, "the captured signal in real");
  28. canvas_draw_str(canvas, 2, 27, "time, like in a CRT TV set.");
  29. canvas_draw_str(canvas, 2, 36, "Use UP/DOWN to change the");
  30. canvas_draw_str(canvas, 2, 45, "resolution (usec/pixel).");
  31. canvas_set_font(canvas, FontPrimary);
  32. canvas_draw_str(canvas, 5, 60, "To start/stop, press OK");
  33. return;
  34. }
  35. privdata->show_usage_info = false;
  36. /* Draw on screen. */
  37. int idx = 0;
  38. for(int y = 0; y < 64; y++) {
  39. for(int x = 0; x < 128; x++) {
  40. bool level = bitmap_get(privdata->captured, CAPTURED_BITMAP_BYTES, idx++);
  41. if(level) canvas_draw_dot(canvas, x, y);
  42. }
  43. }
  44. char buf[32];
  45. snprintf(buf, sizeof(buf), "%lu usec/px", privdata->usec_per_pixel);
  46. canvas_set_font(canvas, FontSecondary);
  47. canvas_draw_str_with_border(canvas, 1, 60, buf, ColorWhite, ColorBlack);
  48. }
  49. /* Handle input */
  50. void process_input_direct_sampling(ProtoViewApp* app, InputEvent input) {
  51. DirectSamplingViewPrivData* privdata = app->view_privdata;
  52. if(input.type == InputTypePress && input.key == InputKeyOk) {
  53. app->direct_sampling_enabled = !app->direct_sampling_enabled;
  54. }
  55. if((input.key == InputKeyUp || input.key == InputKeyDown) &&
  56. (input.type == InputTypePress || input.type == InputTypeRepeat)) {
  57. uint32_t change = input.type == InputTypePress ? USEC_PER_PIXEL_SMALL_CHANGE :
  58. USEC_PER_PIXEL_LARGE_CHANGE;
  59. if(input.key == InputKeyUp) change = -change;
  60. privdata->usec_per_pixel += change;
  61. if(privdata->usec_per_pixel < USEC_PER_PIXEL_MIN)
  62. privdata->usec_per_pixel = USEC_PER_PIXEL_MIN;
  63. else if(privdata->usec_per_pixel > USEC_PER_PIXEL_MAX)
  64. privdata->usec_per_pixel = USEC_PER_PIXEL_MAX;
  65. /* Update the timer frequency. */
  66. direct_sampling_timer_stop(app);
  67. direct_sampling_timer_start(app);
  68. }
  69. }
  70. /* Enter view. Stop the subghz thread to prevent access as we read
  71. * the CC1101 data directly. */
  72. void view_enter_direct_sampling(ProtoViewApp* app) {
  73. /* Set view defaults. */
  74. DirectSamplingViewPrivData* privdata = app->view_privdata;
  75. privdata->usec_per_pixel = DEFAULT_USEC_PER_PIXEL;
  76. privdata->captured = malloc(CAPTURED_BITMAP_BYTES);
  77. privdata->show_usage_info = true;
  78. if(app->txrx->txrx_state == TxRxStateRx && !app->txrx->debug_timer_sampling) {
  79. subghz_devices_stop_async_rx(app->radio_device);
  80. /* To read data asynchronously directly from the view, we need
  81. * to put the CC1101 back into reception mode (the previous call
  82. * to stop the async RX will put it into idle) and configure the
  83. * G0 pin for reading. */
  84. subghz_devices_set_rx(app->radio_device);
  85. furi_hal_gpio_init(
  86. subghz_devices_get_data_gpio(app->radio_device),
  87. GpioModeInput,
  88. GpioPullNo,
  89. GpioSpeedLow);
  90. } else {
  91. raw_sampling_worker_stop(app);
  92. }
  93. // Start the timer to capture raw data
  94. direct_sampling_timer_start(app);
  95. }
  96. /* Exit view. Restore the subghz thread. */
  97. void view_exit_direct_sampling(ProtoViewApp* app) {
  98. DirectSamplingViewPrivData* privdata = app->view_privdata;
  99. if(privdata->captured) free(privdata->captured);
  100. app->direct_sampling_enabled = false;
  101. direct_sampling_timer_stop(app);
  102. /* Restart normal data feeding. */
  103. if(app->txrx->txrx_state == TxRxStateRx && !app->txrx->debug_timer_sampling) {
  104. subghz_devices_start_async_rx(app->radio_device, protoview_rx_callback, NULL);
  105. } else {
  106. furi_hal_gpio_init(
  107. subghz_devices_get_data_gpio(app->radio_device),
  108. GpioModeInput,
  109. GpioPullNo,
  110. GpioSpeedLow);
  111. raw_sampling_worker_start(app);
  112. }
  113. }
  114. /* =========================== Timer implementation ========================= */
  115. static void ds_timer_isr(void* ctx) {
  116. ProtoViewApp* app = ctx;
  117. DirectSamplingViewPrivData* privdata = app->view_privdata;
  118. if(app->direct_sampling_enabled) {
  119. bool level = furi_hal_gpio_read(subghz_devices_get_data_gpio(app->radio_device));
  120. bitmap_set(privdata->captured, CAPTURED_BITMAP_BYTES, privdata->captured_idx, level);
  121. privdata->captured_idx = (privdata->captured_idx + 1) % CAPTURED_BITMAP_BITS;
  122. }
  123. LL_TIM_ClearFlag_UPDATE(TIM2);
  124. }
  125. static void direct_sampling_timer_start(ProtoViewApp* app) {
  126. DirectSamplingViewPrivData* privdata = app->view_privdata;
  127. furi_hal_bus_enable(FuriHalBusTIM2);
  128. LL_TIM_InitTypeDef tim_init = {
  129. .Prescaler = 63, /* CPU frequency is ~64Mhz. */
  130. .CounterMode = LL_TIM_COUNTERMODE_UP,
  131. .Autoreload = privdata->usec_per_pixel};
  132. LL_TIM_Init(TIM2, &tim_init);
  133. LL_TIM_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL);
  134. LL_TIM_DisableCounter(TIM2);
  135. LL_TIM_SetCounter(TIM2, 0);
  136. furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, ds_timer_isr, app);
  137. LL_TIM_EnableIT_UPDATE(TIM2);
  138. LL_TIM_EnableCounter(TIM2);
  139. }
  140. static void direct_sampling_timer_stop(ProtoViewApp* app) {
  141. UNUSED(app);
  142. FURI_CRITICAL_ENTER();
  143. LL_TIM_DisableCounter(TIM2);
  144. LL_TIM_DisableIT_UPDATE(TIM2);
  145. furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL);
  146. furi_hal_bus_disable(FuriHalBusTIM2);
  147. FURI_CRITICAL_EXIT();
  148. }