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"
@@ -10969,12 +10972,15 @@
desc: list acceleration speed
user:
+ 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;
}
}