mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-06-02 18:13:33 -07:00
@@ -2,63 +2,161 @@
|
||||
* See the LICENSE file for information about the license. */
|
||||
|
||||
#include "app.h"
|
||||
|
||||
#include <cc1101.h>
|
||||
|
||||
static void direct_sampling_timer_start(ProtoViewApp* app);
|
||||
static void direct_sampling_timer_stop(ProtoViewApp* app);
|
||||
|
||||
#define CAPTURED_BITMAP_BITS (128 * 64)
|
||||
#define CAPTURED_BITMAP_BYTES (CAPTURED_BITMAP_BITS / 8)
|
||||
#define DEFAULT_USEC_PER_PIXEL 50
|
||||
#define USEC_PER_PIXEL_SMALL_CHANGE 5
|
||||
#define USEC_PER_PIXEL_LARGE_CHANGE 25
|
||||
#define USEC_PER_PIXEL_MIN 5
|
||||
#define USEC_PER_PIXEL_MAX 300
|
||||
typedef struct {
|
||||
uint8_t* captured; // Bitmap with the last captured screen.
|
||||
uint32_t captured_idx; // Current index to write into the bitmap
|
||||
uint32_t usec_per_pixel; // Number of useconds a pixel should represent
|
||||
bool show_usage_info;
|
||||
} DirectSamplingViewPrivData;
|
||||
|
||||
/* Read directly from the G0 CC1101 pin, and draw a black or white
|
||||
* dot depending on the level. */
|
||||
void render_view_direct_sampling(Canvas* const canvas, ProtoViewApp* app) {
|
||||
if(!app->direct_sampling_enabled) {
|
||||
DirectSamplingViewPrivData* privdata = app->view_privdata;
|
||||
|
||||
if(!app->direct_sampling_enabled && privdata->show_usage_info) {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(canvas, 2, 9, "Direct sampling is a special");
|
||||
canvas_draw_str(canvas, 2, 18, "mode that displays the signal");
|
||||
canvas_draw_str(canvas, 2, 27, "captured in real time. Like in");
|
||||
canvas_draw_str(canvas, 2, 36, "a old CRT TV. It's very slow.");
|
||||
canvas_draw_str(canvas, 2, 45, "Can crash your Flipper.");
|
||||
canvas_draw_str(canvas, 2, 9, "Direct sampling displays the");
|
||||
canvas_draw_str(canvas, 2, 18, "the captured signal in real");
|
||||
canvas_draw_str(canvas, 2, 27, "time, like in a CRT TV set.");
|
||||
canvas_draw_str(canvas, 2, 36, "Use UP/DOWN to change the");
|
||||
canvas_draw_str(canvas, 2, 45, "resolution (usec/pixel).");
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 14, 60, "To enable press OK");
|
||||
canvas_draw_str(canvas, 5, 60, "To start/stop, press OK");
|
||||
return;
|
||||
}
|
||||
privdata->show_usage_info = false;
|
||||
|
||||
/* Draw on screen. */
|
||||
int idx = 0;
|
||||
for(int y = 0; y < 64; y++) {
|
||||
for(int x = 0; x < 128; x++) {
|
||||
bool level = furi_hal_gpio_read(&gpio_cc1101_g0);
|
||||
bool level = bitmap_get(privdata->captured, CAPTURED_BITMAP_BYTES, idx++);
|
||||
if(level) canvas_draw_dot(canvas, x, y);
|
||||
/* Busy loop: this is a terrible approach as it blocks
|
||||
* everything else, but for now it's the best we can do
|
||||
* to obtain direct data with some spacing. */
|
||||
uint32_t x = 250;
|
||||
while(x--)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof(buf), "%lu usec/px", privdata->usec_per_pixel);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_with_border(canvas, 36, 60, "Direct sampling", ColorWhite, ColorBlack);
|
||||
canvas_draw_str_with_border(canvas, 1, 60, buf, ColorWhite, ColorBlack);
|
||||
}
|
||||
|
||||
/* Handle input */
|
||||
void process_input_direct_sampling(ProtoViewApp* app, InputEvent input) {
|
||||
DirectSamplingViewPrivData* privdata = app->view_privdata;
|
||||
|
||||
if(input.type == InputTypePress && input.key == InputKeyOk) {
|
||||
app->direct_sampling_enabled = !app->direct_sampling_enabled;
|
||||
}
|
||||
|
||||
if((input.key == InputKeyUp || input.key == InputKeyDown) &&
|
||||
(input.type == InputTypePress || input.type == InputTypeRepeat)) {
|
||||
uint32_t change = input.type == InputTypePress ? USEC_PER_PIXEL_SMALL_CHANGE :
|
||||
USEC_PER_PIXEL_LARGE_CHANGE;
|
||||
if(input.key == InputKeyUp) change = -change;
|
||||
privdata->usec_per_pixel += change;
|
||||
if(privdata->usec_per_pixel < USEC_PER_PIXEL_MIN)
|
||||
privdata->usec_per_pixel = USEC_PER_PIXEL_MIN;
|
||||
else if(privdata->usec_per_pixel > USEC_PER_PIXEL_MAX)
|
||||
privdata->usec_per_pixel = USEC_PER_PIXEL_MAX;
|
||||
/* Update the timer frequency. */
|
||||
direct_sampling_timer_stop(app);
|
||||
direct_sampling_timer_start(app);
|
||||
}
|
||||
}
|
||||
|
||||
/* Enter view. Stop the subghz thread to prevent access as we read
|
||||
* the CC1101 data directly. */
|
||||
void view_enter_direct_sampling(ProtoViewApp* app) {
|
||||
/* Set view defaults. */
|
||||
DirectSamplingViewPrivData* privdata = app->view_privdata;
|
||||
privdata->usec_per_pixel = DEFAULT_USEC_PER_PIXEL;
|
||||
privdata->captured = malloc(CAPTURED_BITMAP_BYTES);
|
||||
privdata->show_usage_info = true;
|
||||
|
||||
if(app->txrx->txrx_state == TxRxStateRx && !app->txrx->debug_timer_sampling) {
|
||||
subghz_worker_stop(app->txrx->worker);
|
||||
furi_hal_subghz_stop_async_rx();
|
||||
|
||||
/* To read data asynchronously directly from the view, we need
|
||||
* to put the CC1101 back into reception mode (the previous call
|
||||
* to stop the async RX will put it into idle) and configure the
|
||||
* G0 pin for reading. */
|
||||
furi_hal_subghz_rx();
|
||||
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
|
||||
} else {
|
||||
raw_sampling_worker_stop(app);
|
||||
}
|
||||
|
||||
// Start the timer to capture raw data
|
||||
direct_sampling_timer_start(app);
|
||||
}
|
||||
|
||||
/* Exit view. Restore the subghz thread. */
|
||||
void view_exit_direct_sampling(ProtoViewApp* app) {
|
||||
DirectSamplingViewPrivData* privdata = app->view_privdata;
|
||||
if(privdata->captured) free(privdata->captured);
|
||||
app->direct_sampling_enabled = false;
|
||||
|
||||
direct_sampling_timer_stop(app);
|
||||
|
||||
/* Restart normal data feeding. */
|
||||
if(app->txrx->txrx_state == TxRxStateRx && !app->txrx->debug_timer_sampling) {
|
||||
subghz_worker_start(app->txrx->worker);
|
||||
furi_hal_subghz_start_async_rx(protoview_rx_callback, NULL);
|
||||
} else {
|
||||
raw_sampling_worker_start(app);
|
||||
}
|
||||
app->direct_sampling_enabled = false;
|
||||
}
|
||||
|
||||
/* =========================== Timer implementation ========================= */
|
||||
|
||||
static void ds_timer_isr(void* ctx) {
|
||||
ProtoViewApp* app = ctx;
|
||||
DirectSamplingViewPrivData* privdata = app->view_privdata;
|
||||
|
||||
if(app->direct_sampling_enabled) {
|
||||
bool level = furi_hal_gpio_read(&gpio_cc1101_g0);
|
||||
bitmap_set(privdata->captured, CAPTURED_BITMAP_BYTES, privdata->captured_idx, level);
|
||||
privdata->captured_idx = (privdata->captured_idx + 1) % CAPTURED_BITMAP_BITS;
|
||||
}
|
||||
LL_TIM_ClearFlag_UPDATE(TIM2);
|
||||
}
|
||||
|
||||
static void direct_sampling_timer_start(ProtoViewApp* app) {
|
||||
DirectSamplingViewPrivData* privdata = app->view_privdata;
|
||||
|
||||
LL_TIM_InitTypeDef tim_init = {
|
||||
.Prescaler = 63, /* CPU frequency is ~64Mhz. */
|
||||
.CounterMode = LL_TIM_COUNTERMODE_UP,
|
||||
.Autoreload = privdata->usec_per_pixel};
|
||||
|
||||
LL_TIM_Init(TIM2, &tim_init);
|
||||
LL_TIM_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL);
|
||||
LL_TIM_DisableCounter(TIM2);
|
||||
LL_TIM_SetCounter(TIM2, 0);
|
||||
furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, ds_timer_isr, app);
|
||||
LL_TIM_EnableIT_UPDATE(TIM2);
|
||||
LL_TIM_EnableCounter(TIM2);
|
||||
}
|
||||
|
||||
static void direct_sampling_timer_stop(ProtoViewApp* app) {
|
||||
UNUSED(app);
|
||||
FURI_CRITICAL_ENTER();
|
||||
LL_TIM_DisableCounter(TIM2);
|
||||
LL_TIM_DisableIT_UPDATE(TIM2);
|
||||
furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL);
|
||||
LL_TIM_DeInit(TIM2);
|
||||
FURI_CRITICAL_EXIT();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user