diff --git a/apps/action.c b/apps/action.c index 826f376dcd..9afdf37af6 100644 --- a/apps/action.c +++ b/apps/action.c @@ -31,6 +31,7 @@ static int last_button = BUTTON_NONE|BUTTON_REL; /* allow the ipod wheel to work on startup */ +static intptr_t last_data = 0; static int last_action = ACTION_NONE; static bool repeated = false; @@ -113,6 +114,7 @@ static int get_action_worker(int context, int timeout, else button = button_get_w_tmo(timeout); + /* Data from sys events can be pulled with button_get_data */ if (button == BUTTON_NONE || button&SYS_EVENT) { return button; @@ -201,6 +203,7 @@ static int get_action_worker(int context, int timeout, last_button = button; last_action = ret; + last_data = button_get_data(); last_action_tick = current_tick; return ret; } @@ -230,6 +233,11 @@ bool is_keys_locked(void) } #endif +intptr_t get_action_data(void) +{ + return last_data; +} + int get_action_statuscode(int *button) { int ret = 0; diff --git a/apps/action.h b/apps/action.h index 7acaf9c005..5beacaa0e2 100644 --- a/apps/action.h +++ b/apps/action.h @@ -253,6 +253,8 @@ bool is_keys_locked(void); #define ACTION_REPEAT 0x2 /* action was repeated (NOT button) */ int get_action_statuscode(int *button); +/* returns the data value associated with the last action that is not + BUTTON_NONE or flagged with SYS_EVENT */ +intptr_t get_action_data(void); - -#endif +#endif /* __ACTION_H__ */ diff --git a/apps/gui/list.c b/apps/gui/list.c index 2fb531b6bd..0cc3257246 100644 --- a/apps/gui/list.c +++ b/apps/gui/list.c @@ -887,15 +887,22 @@ static void gui_synclist_scroll_left(struct gui_synclist * lists) } #endif /* HAVE_LCD_BITMAP */ +extern intptr_t get_action_data(void); + unsigned gui_synclist_do_button(struct gui_synclist * lists, unsigned button,enum list_wrap wrap) { #ifdef HAVE_LCD_BITMAP static bool scrolling_left = false; #endif + int i; + +#ifdef HAVE_SCROLLWHEEL + int next_item_modifier = button_apply_acceleration(get_action_data(), + WHEEL_ACCELERATION_FACTOR); +#else static int next_item_modifier = 1; static int last_accel_tick = 0; - int i; if (global_settings.list_accel_start_delay) { @@ -919,6 +926,7 @@ unsigned gui_synclist_do_button(struct gui_synclist * lists, last_accel_tick = 0; } } +#endif switch (wrap) { @@ -953,8 +961,12 @@ unsigned gui_synclist_do_button(struct gui_synclist * lists, case ACTION_STD_PREVREPEAT: FOR_NB_SCREENS(i) gui_list_select_at_offset(&(lists->gui_list[i]), -next_item_modifier); +#ifndef HAVE_SCROLLWHEEL if (queue_count(&button_queue) < FRAMEDROP_TRIGGER) +#endif + { gui_synclist_draw(lists); + } yield(); return ACTION_STD_PREV; @@ -962,8 +974,12 @@ unsigned gui_synclist_do_button(struct gui_synclist * lists, case ACTION_STD_NEXTREPEAT: FOR_NB_SCREENS(i) gui_list_select_at_offset(&(lists->gui_list[i]), next_item_modifier); +#ifndef HAVE_SCROLLWHEEL if (queue_count(&button_queue) < FRAMEDROP_TRIGGER) +#endif + { gui_synclist_draw(lists); + } yield(); return ACTION_STD_NEXT; diff --git a/apps/lang/english.lang b/apps/lang/english.lang index f4577d9b2d..ca150768f3 100644 --- a/apps/lang/english.lang +++ b/apps/lang/english.lang @@ -10955,12 +10955,15 @@ desc: Delay before list starts accelerating user: + e200: "" *: "List Acceleration Start Delay" + e200: "" *: "List Acceleration Start Delay" + e200: "" *: "List Acceleration Start Delay" @@ -10969,12 +10972,15 @@ desc: list acceleration speed user: + e200: "" *: "List Acceleration Speed" + e200: "" *: "List Acceleration Speed" + e200: "" *: "List Acceleration Speed" diff --git a/apps/menus/display_menu.c b/apps/menus/display_menu.c index 5114feafb0..92afc304db 100644 --- a/apps/menus/display_menu.c +++ b/apps/menus/display_menu.c @@ -313,9 +313,11 @@ MENUITEM_SETTING(jump_scroll, &global_settings.jump_scroll, NULL); MENUITEM_SETTING(jump_scroll_delay, &global_settings.jump_scroll_delay, NULL); #endif /* list acceleration */ +#ifndef HAVE_SCROLLWHEEL MENUITEM_SETTING(list_accel_start_delay, &global_settings.list_accel_start_delay, NULL); MENUITEM_SETTING(list_accel_wait, &global_settings.list_accel_wait, NULL); +#endif /* HAVE_SCROLLWHEEL */ #ifdef HAVE_LCD_BITMAP int screenscroll_callback(int action,const struct menu_item_ex *this_item) { @@ -350,7 +352,9 @@ MAKE_MENU(scroll_settings_menu, ID2P(LANG_SCROLL_MENU), 0, Icon_NOICON, &offset_out_of_view, &screen_scroll_step, #endif &scroll_paginated, +#ifndef HAVE_SCROLLWHEEL &list_accel_start_delay, &list_accel_wait +#endif ); /* SCROLL MENU */ /***********************************/ diff --git a/apps/settings.h b/apps/settings.h index e9018141a3..f9ee1df7ec 100644 --- a/apps/settings.h +++ b/apps/settings.h @@ -731,8 +731,10 @@ struct user_settings #ifdef HAVE_BUTTONLIGHT_BRIGHTNESS int buttonlight_brightness; #endif +#ifndef HAVE_SCROLLWHEEL int list_accel_start_delay; /* ms before we start increaseing step size */ int list_accel_wait; /* ms between increases */ +#endif }; /** global variables **/ diff --git a/apps/settings_list.c b/apps/settings_list.c index ae32735733..76a3e41b60 100644 --- a/apps/settings_list.c +++ b/apps/settings_list.c @@ -274,6 +274,7 @@ static void poweroff_idle_timer_formatter(char *buffer, int buffer_size, snprintf(buffer, buffer_size, "%dm", poweroff_idle_timer_times[val]); } +#ifndef HAVE_SCROLLWHEEL static long listaccel_getlang(int value) { if (value == 0) @@ -289,6 +290,7 @@ static void listaccel_formatter(char *buffer, int buffer_size, else snprintf(buffer, buffer_size, "%d ms", 5*HZ*val); } +#endif /* HAVE_SCROLLWHEEL */ #if CONFIG_CODEC == SWCODEC static void crossfeed_format(char* buffer, int buffer_size, int value, @@ -1235,12 +1237,14 @@ const struct settings_list settings[] = { "button light brightness",UNIT_INT, MIN_BRIGHTNESS_SETTING, MAX_BRIGHTNESS_SETTING, 1, NULL, NULL, buttonlight_set_brightness), #endif +#ifndef HAVE_SCROLLWHEEL INT_SETTING(0, list_accel_start_delay, LANG_LISTACCEL_START_DELAY, 2, "list_accel_start_delay", UNIT_MS, 0, 10, 1, listaccel_formatter, listaccel_getlang, NULL), INT_SETTING(0, list_accel_wait, LANG_LISTACCEL_ACCEL_SPEED, 3, "list_accel_wait", UNIT_SEC, 1, 10, 1, scanaccel_formatter, scanaccel_getlang, NULL), +#endif /* HAVE_SCROLLWHEEL */ }; const int nb_settings = sizeof(settings)/sizeof(*settings); diff --git a/firmware/drivers/button.c b/firmware/drivers/button.c index f9f355ce37..715d4d3025 100644 --- a/firmware/drivers/button.c +++ b/firmware/drivers/button.c @@ -49,6 +49,7 @@ struct event_queue button_queue; static long lastbtn; /* Last valid button status */ static long last_read; /* Last button status, for debouncing/filtering */ +static intptr_t button_data; /* data value from last message dequeued */ #ifdef HAVE_LCD_BITMAP static bool flipped; /* buttons can be flipped to match the LCD flip */ #endif @@ -309,7 +310,7 @@ long button_get(bool block) if (current_tick - ev.tick > MAX_EVENT_AGE) return BUTTON_NONE; #endif - + button_data = ev.data; return ev.id; } @@ -329,7 +330,17 @@ long button_get_w_tmo(int ticks) #endif queue_wait_w_tmo(&button_queue, &ev, ticks); - return (ev.id != SYS_TIMEOUT)? ev.id: BUTTON_NONE; + if (ev.id == SYS_TIMEOUT) + ev.id = BUTTON_NONE; + else + button_data = ev.data; + + return ev.id; +} + +intptr_t button_get_data(void) +{ + return button_data; } void button_init(void) @@ -470,3 +481,33 @@ void button_clear_queue(void) { queue_clear(&button_queue); } + +#ifdef HAVE_SCROLLWHEEL +/** + * data: + * [31] Use acceleration + * [30:24] Message post count (skipped + 1) (1-127) + * [23:0] Velocity - clicks/uS - 0.24 fixed point + * + * factor: + * Wheel acceleration scaling factor - x.24 fixed point - + * no greater than what will not overflow 64 bits when multiplied + * by the driver's maximum velocity in (clicks/usec)^2 in 0.24 + */ +int button_apply_acceleration(unsigned int data, unsigned int factor) +{ + int delta = (data >> 24) & 0x7f; + + if ((data & (1 << 31)) != 0) + { + unsigned int v = data & 0xffffff; + + v = factor * (unsigned long long)v*v / 0xffffffffffffull; + + if (v > 1) + delta *= v; + } + + return delta; +} +#endif /* HAVE_SCROLLWHEEL */ diff --git a/firmware/export/button.h b/firmware/export/button.h index f675824c3d..8b8c966ddc 100644 --- a/firmware/export/button.h +++ b/firmware/export/button.h @@ -20,6 +20,7 @@ #define _BUTTON_H_ #include +#include #include "config.h" #include "button-target.h" @@ -28,6 +29,7 @@ extern struct event_queue button_queue; void button_init (void); long button_get (bool block); long button_get_w_tmo(int ticks); +intptr_t button_get_data(void); int button_status(void); void button_clear_queue(void); #ifdef HAVE_LCD_BITMAP @@ -48,6 +50,10 @@ int wheel_status(void); void wheel_send_events(bool send); #endif +#ifdef HAVE_SCROLLWHEEL +int button_apply_acceleration(unsigned int data, unsigned int factor); +#endif + #define BUTTON_NONE 0x00000000 /* Button modifiers */ diff --git a/firmware/export/config-e200.h b/firmware/export/config-e200.h index 6edab71fbf..c7f058e95c 100644 --- a/firmware/export/config-e200.h +++ b/firmware/export/config-e200.h @@ -170,4 +170,10 @@ #define ICODE_ATTR_TREMOR_NOT_MDCT -#endif +/* define this if the unit uses a scrollwheel for navigation */ +#define HAVE_SCROLLWHEEL +/* define wheel acceleration scaling factor */ +/* Range for this target: 0xffffff*(0.0-16.000000894069724921567733381255) */ +#define WHEEL_ACCELERATION_FACTOR (0xffffff*7) + +#endif /* SIMULATOR */ diff --git a/firmware/target/arm/sandisk/sansa-e200/button-e200.c b/firmware/target/arm/sandisk/sansa-e200/button-e200.c index 5e2c38e8e3..734416bf28 100644 --- a/firmware/target/arm/sandisk/sansa-e200/button-e200.c +++ b/firmware/target/arm/sandisk/sansa-e200/button-e200.c @@ -23,19 +23,21 @@ #include "button.h" #include "backlight.h" -#define WHEEL_REPEAT_INTERVAL 30 -#define WHEEL_FAST_ON_INTERVAL 2 -#define WHEEL_FAST_OFF_INTERVAL 6 +#define WHEEL_REPEAT_INTERVAL 300000 +#define WHEEL_FAST_ON_INTERVAL 20000 +#define WHEEL_FAST_OFF_INTERVAL 60000 /* Clickwheel */ #ifndef BOOTLOADER static unsigned int old_wheel_value = 0; static unsigned int wheel_repeat = BUTTON_NONE; static unsigned int wheel_click_count = 0; +static unsigned int wheel_delta = 0; static int wheel_fast_mode = 0; -static unsigned long last_wheel_tick = 0; -static unsigned long last_wheel_post = 0; -static unsigned long next_backlight_on = 0; +static unsigned long last_wheel_usec = 0; +static unsigned long wheel_velocity = 0; +static long last_wheel_post = 0; +static long next_backlight_on = 0; /* Buttons */ static bool hold_button = false; static bool hold_button_old = false; @@ -64,8 +66,8 @@ void button_init_device(void) GPIOH_INT_EN &= ~0xc0; /* Get current tick before enabling button interrupts */ - last_wheel_tick = current_tick; - last_wheel_post = current_tick; + last_wheel_usec = USEC_TIMER; + last_wheel_post = last_wheel_usec; GPIOH_ENABLE |= 0xc0; GPIOH_OUTPUT_EN &= ~0xc0; @@ -130,38 +132,67 @@ void clickwheel_int(void) if (btn != BUTTON_NONE) { - int repeat = 1; + int repeat = 1; /* assume repeat */ + unsigned long usec = USEC_TIMER; + unsigned v = (usec - last_wheel_usec) & 0x7fffffff; + + /* wheel velocity in 0.24 fixed point - clicks/uS */ + + /* velocity cap to 18 bits to allow up to x16 scaling */ + v = (v < 0x40) ? 0xffffff / 0x40 : 0xffffff / v; + + /* some velocity filtering to smooth things out */ + wheel_velocity = (7*wheel_velocity + v) / 8; if (btn != wheel_repeat) { + /* direction reversals nullify all fast mode states */ wheel_repeat = btn; repeat = wheel_fast_mode = + wheel_velocity = wheel_click_count = 0; } - if (wheel_fast_mode) + if (wheel_fast_mode != 0) { - if (TIME_AFTER(current_tick, - last_wheel_tick + WHEEL_FAST_OFF_INTERVAL)) + /* fast OFF happens immediately when velocity drops below + threshold */ + if (TIME_AFTER(usec, + last_wheel_usec + WHEEL_FAST_OFF_INTERVAL)) { - if (++wheel_click_count < 2) - btn = BUTTON_NONE; + /* moving out of fast mode */ wheel_fast_mode = 0; + /* reset velocity */ + wheel_velocity = 0; + /* wheel_delta is always 1 in slow mode */ + wheel_delta = 1; } } else { - if (repeat && TIME_BEFORE(current_tick, - last_wheel_tick + WHEEL_FAST_ON_INTERVAL)) - wheel_fast_mode = 1; + /* fast ON gets filtered to avoid inadvertent jumps to fast mode */ + if (repeat && wheel_velocity > 0xffffff/WHEEL_FAST_ON_INTERVAL) + { + /* moving into fast mode */ + wheel_fast_mode = 1 << 31; + wheel_click_count = 0; + wheel_velocity = 0xffffff/WHEEL_FAST_OFF_INTERVAL; + } else if (++wheel_click_count < 2) + { btn = BUTTON_NONE; + } + + /* wheel_delta is always 1 in slow mode */ + wheel_delta = 1; } - if (TIME_AFTER(current_tick, next_backlight_on)) + if (TIME_AFTER(usec, next_backlight_on)) { - next_backlight_on = current_tick + HZ/4; + /* poke backlight to turn it on or maintain it no more often + than every 1/4 second*/ + next_backlight_on = usec + 1000000/4; backlight_on(); button_backlight_on(); } @@ -170,17 +201,29 @@ void clickwheel_int(void) { wheel_click_count = 0; - if (repeat && TIME_BEFORE(current_tick, - last_wheel_post + WHEEL_REPEAT_INTERVAL)) + /* generate repeats if quick enough */ + if (repeat && TIME_BEFORE(usec, + last_wheel_post + WHEEL_REPEAT_INTERVAL)) btn |= BUTTON_REPEAT; - last_wheel_post = current_tick; + last_wheel_post = usec; if (queue_empty(&button_queue)) - queue_post(&button_queue, btn, 0); + { + queue_post(&button_queue, btn, wheel_fast_mode | + (wheel_delta << 24) | wheel_velocity); + /* message posted - reset delta */ + wheel_delta = 1; + } + else + { + /* skipped post - increment delta */ + if (++wheel_delta > 0x7f) + wheel_delta = 0x7f; + } } - last_wheel_tick = current_tick; + last_wheel_usec = usec; } }