diff --git a/apps/gui/list.c b/apps/gui/list.c index 223fc2ccb6..c43020113b 100644 --- a/apps/gui/list.c +++ b/apps/gui/list.c @@ -1011,8 +1011,7 @@ bool gui_synclist_do_button(struct gui_synclist * lists, int i; #ifdef HAVE_SCROLLWHEEL - int next_item_modifier = button_apply_acceleration(get_action_data(), - WHEEL_ACCELERATION_FACTOR); + int next_item_modifier = button_apply_acceleration(get_action_data()); #else static int next_item_modifier = 1; static int last_accel_tick = 0; diff --git a/firmware/drivers/button.c b/firmware/drivers/button.c index 000c4789a5..c7d532ebde 100644 --- a/firmware/drivers/button.c +++ b/firmware/drivers/button.c @@ -522,26 +522,40 @@ void button_clear_queue(void) #endif /* SIMULATOR */ #ifdef HAVE_SCROLLWHEEL +/* WHEEL_ACCEL_FACTOR = 2^16 / WHEEL_ACCEL_START */ +#define WHEEL_ACCEL_FACTOR (1<<16)/WHEEL_ACCEL_START /** * data: * [31] Use acceleration * [30:24] Message post count (skipped + 1) (1-127) - * [23:0] Velocity - clicks/uS - 0.24 fixed point + * [23:0] Velocity - degree/sec * - * 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 + * WHEEL_ACCEL_FACTOR: + * Value in degree/sec -- configurable via settings -- above which + * the accelerated scrolling starts. Factor is internally scaled by + * 1<<16 in respect to the following 32bit integer operations. */ -int button_apply_acceleration(unsigned int data, unsigned int factor) +int button_apply_acceleration(const unsigned int data) { int delta = (data >> 24) & 0x7f; if ((data & (1 << 31)) != 0) { + /* read driver's velocity from data */ unsigned int v = data & 0xffffff; - v = factor * (unsigned long long)v*v / 0xffffffffffffull; + /* v = 28.4 fixed point */ + v = (WHEEL_ACCEL_FACTOR * v)>>(16-4); + + /* Calculate real numbers item to scroll based upon acceleration + * setting, use correct roundoff */ +#if (WHEEL_ACCELERATION == 1) + v = (v*v + (1<< 7))>> 8; +#elif (WHEEL_ACCELERATION == 2) + v = (v*v*v + (1<<11))>>12; +#elif (WHEEL_ACCELERATION == 3) + v = (v*v*v*v + (1<<15))>>16; +#endif if (v > 1) delta *= v; diff --git a/firmware/export/button.h b/firmware/export/button.h index a38385c4cf..f50f3dd9a4 100644 --- a/firmware/export/button.h +++ b/firmware/export/button.h @@ -52,7 +52,7 @@ void wheel_send_events(bool send); #endif #ifdef HAVE_SCROLLWHEEL -int button_apply_acceleration(unsigned int data, unsigned int factor); +int button_apply_acceleration(const unsigned int data); #endif #define BUTTON_NONE 0x00000000 diff --git a/firmware/export/config-e200.h b/firmware/export/config-e200.h index e2274bc956..f5ba8f0c4d 100644 --- a/firmware/export/config-e200.h +++ b/firmware/export/config-e200.h @@ -100,9 +100,10 @@ /* 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) +/* define from which rotation speed [degree/sec] on the acceleration starts */ +#define WHEEL_ACCEL_START 540 +/* define type of acceleration (1 = ^2, 2 = ^3, 3 = ^4) */ +#define WHEEL_ACCELERATION 1 /* define this if you have a flash memory storage */ #define HAVE_FLASH_STORAGE diff --git a/firmware/export/config-ipod4g.h b/firmware/export/config-ipod4g.h index fc280502d0..7ed790185d 100644 --- a/firmware/export/config-ipod4g.h +++ b/firmware/export/config-ipod4g.h @@ -86,6 +86,13 @@ /* Define this for LCD backlight available */ #define HAVE_BACKLIGHT +/* define this if the unit uses a scrollwheel for navigation */ +#define HAVE_SCROLLWHEEL +/* define from which rotation speed [degree/sec] on the acceleration starts */ +#define WHEEL_ACCEL_START 270 +/* define type of acceleration (1 = ^2, 2 = ^3, 3 = ^4) */ +#define WHEEL_ACCELERATION 3 + /* Define this if you can detect headphones */ #define HAVE_HEADPHONE_DETECTION diff --git a/firmware/export/config-ipodcolor.h b/firmware/export/config-ipodcolor.h index 0aba8c34d6..25a7fb3098 100644 --- a/firmware/export/config-ipodcolor.h +++ b/firmware/export/config-ipodcolor.h @@ -73,6 +73,13 @@ /* Define this for LCD backlight available */ #define HAVE_BACKLIGHT +/* define this if the unit uses a scrollwheel for navigation */ +#define HAVE_SCROLLWHEEL +/* define from which rotation speed [degree/sec] on the acceleration starts */ +#define WHEEL_ACCEL_START 270 +/* define type of acceleration (1 = ^2, 2 = ^3, 3 = ^4) */ +#define WHEEL_ACCELERATION 3 + /* Define this if you can detect headphones */ #define HAVE_HEADPHONE_DETECTION diff --git a/firmware/export/config-ipodmini2g.h b/firmware/export/config-ipodmini2g.h index 5dd2293583..0d3dceb73b 100644 --- a/firmware/export/config-ipodmini2g.h +++ b/firmware/export/config-ipodmini2g.h @@ -83,6 +83,13 @@ /* We can fade the backlight by using PWM */ #define HAVE_BACKLIGHT_PWM_FADING +/* define this if the unit uses a scrollwheel for navigation */ +#define HAVE_SCROLLWHEEL +/* define from which rotation speed [degree/sec] on the acceleration starts */ +#define WHEEL_ACCEL_START 270 +/* define type of acceleration (1 = ^2, 2 = ^3, 3 = ^4) */ +#define WHEEL_ACCELERATION 3 + /* Define this if you can detect headphones */ #define HAVE_HEADPHONE_DETECTION diff --git a/firmware/export/config-ipodnano.h b/firmware/export/config-ipodnano.h index 69095614dc..27e159471e 100644 --- a/firmware/export/config-ipodnano.h +++ b/firmware/export/config-ipodnano.h @@ -82,6 +82,13 @@ /* We can fade the backlight by using PWM */ #define HAVE_BACKLIGHT_PWM_FADING +/* define this if the unit uses a scrollwheel for navigation */ +#define HAVE_SCROLLWHEEL +/* define from which rotation speed [degree/sec] on the acceleration starts */ +#define WHEEL_ACCEL_START 270 +/* define type of acceleration (1 = ^2, 2 = ^3, 3 = ^4) */ +#define WHEEL_ACCELERATION 3 + /* Define this if you can detect headphones */ #define HAVE_HEADPHONE_DETECTION diff --git a/firmware/export/config-ipodvideo.h b/firmware/export/config-ipodvideo.h index 60a5dd8f6f..c66f6d9aff 100644 --- a/firmware/export/config-ipodvideo.h +++ b/firmware/export/config-ipodvideo.h @@ -82,6 +82,13 @@ /* We can fade the backlight by using PWM */ #define HAVE_BACKLIGHT_PWM_FADING +/* define this if the unit uses a scrollwheel for navigation */ +#define HAVE_SCROLLWHEEL +/* define from which rotation speed [degree/sec] on the acceleration starts */ +#define WHEEL_ACCEL_START 270 +/* define type of acceleration (1 = ^2, 2 = ^3, 3 = ^4) */ +#define WHEEL_ACCELERATION 3 + /* Define this if you can detect headphones */ #define HAVE_HEADPHONE_DETECTION diff --git a/firmware/target/arm/ipod/button-clickwheel.c b/firmware/target/arm/ipod/button-clickwheel.c index 15f3cdd43c..f01666a8b8 100644 --- a/firmware/target/arm/ipod/button-clickwheel.c +++ b/firmware/target/arm/ipod/button-clickwheel.c @@ -40,6 +40,30 @@ #include "system.h" #include "powermgmt.h" +#define WHEEL_FAST_OFF_TIMEOUT 250000 /* timeout for acceleration = 250ms */ +#define WHEEL_REPEAT_TIMEOUT 250000 /* timeout for button repeat = 250ms */ +#define WHEEL_UNTOUCH_TIMEOUT 150000 /* timeout for untouching wheel = 100ms */ +#define WHEELCLICKS_PER_ROTATION 96 /* wheelclicks per full rotation */ + +/* This amount of clicks is needed for at least scrolling 1 item. Choose small values + * to have high sensitivity but few precision, choose large values to have less + * sensitivity and good precision. */ +#if defined(IPOD_NANO) +#define WHEEL_SENSITIVITY 6 /* iPod nano has smaller wheel, lower sensitivity needed */ +#else +#define WHEEL_SENSITIVITY 4 /* default sensitivity */ +#endif + +int old_wheel_value = -1; +int new_wheel_value = 0; +int repeat = 0; +int wheel_delta = 0; +bool wheel_is_touched = false; +unsigned int accumulated_wheel_delta = 0; +unsigned int wheel_repeat = 0; +unsigned int wheel_velocity = 0; +unsigned long last_wheel_usec = 0; + /* Variable to use for setting button status in interrupt handler */ int int_btn = BUTTON_NONE; #ifdef HAVE_WHEEL_POSITION @@ -89,16 +113,15 @@ static inline int ipod_4g_button_read(void) int btn = BUTTON_NONE; unsigned reg = 0x7000c104; - if ((inl(0x7000c104) & 0x4000000) != 0) { + if ((inl(0x7000c104) & 0x4000000) != 0) + { unsigned status = inl(0x7000c140); reg = reg + 0x3C; /* 0x7000c140 */ outl(0x0, 0x7000c140); /* clear interrupt status? */ - if ((status & 0x800000ff) == 0x8000001a) { - static int old_wheel_value IDATA_ATTR = -1; - static int wheel_repeat = 0; - + if ((status & 0x800000ff) == 0x8000001a) + { if (status & 0x100) btn |= BUTTON_SELECT; if (status & 0x200) @@ -109,63 +132,152 @@ static inline int ipod_4g_button_read(void) btn |= BUTTON_PLAY; if (status & 0x1000) btn |= BUTTON_MENU; - if (status & 0x40000000) { - /* NB: highest wheel = 0x5F, clockwise increases */ - int new_wheel_value = (status << 9) >> 25; + if (status & 0x40000000) + { + unsigned long usec = USEC_TIMER; + + /* Highest wheel = 0x5F, clockwise increases */ + new_wheel_value = (status >> 16) & 0x7f; whl = new_wheel_value; + + /* switch on backlight (again), reset power-off timer */ backlight_on(); reset_poweroff_timer(); - /* The queue should have no other events when scrolling */ - if (queue_empty(&button_queue) && old_wheel_value >= 0) { - - /* This is for later = BUTTON_SCROLL_TOUCH;*/ - int wheel_delta = new_wheel_value - old_wheel_value; - unsigned long data; - int wheel_keycode; - - if (wheel_delta < -48) - wheel_delta += 96; /* Forward wrapping case */ - else if (wheel_delta > 48) - wheel_delta -= 96; /* Backward wrapping case */ - - if (wheel_delta > 4) { - wheel_keycode = BUTTON_SCROLL_FWD; - } else if (wheel_delta < -4) { - wheel_keycode = BUTTON_SCROLL_BACK; - } else goto wheel_end; - -#ifdef HAVE_WHEEL_POSITION - if (send_events) -#endif - { - data = (wheel_delta << 16) | new_wheel_value; - queue_post(&button_queue, wheel_keycode | wheel_repeat, - data); - } - - if (!wheel_repeat) wheel_repeat = BUTTON_REPEAT; + + /* Check whether the scrollwheel was untouched by accident or by will. */ + /* This is needed because wheel may be untoched very shortly during rotation */ + if ( (!wheel_is_touched) && TIME_AFTER(usec, last_wheel_usec + WHEEL_UNTOUCH_TIMEOUT) ) + { + /* wheel has been really untouched -> reset internal variables */ + old_wheel_value = -1; + wheel_velocity = 0; + accumulated_wheel_delta = 0; + wheel_repeat = BUTTON_NONE; + } + else + { + /* wheel was shortly untouched by accident -> leave internal variables */ + wheel_is_touched = true; } - old_wheel_value = new_wheel_value; - } else if (old_wheel_value >= 0) { - /* scroll wheel up */ - old_wheel_value = -1; - wheel_repeat = 0; + if (old_wheel_value >= 0) + { + /* This is for later = BUTTON_SCROLL_TOUCH;*/ + wheel_delta = new_wheel_value - old_wheel_value; + unsigned int wheel_keycode = BUTTON_NONE; + + /* Taking into account wrapping during transition from highest + * to lowest wheel position and back */ + if (wheel_delta < -WHEELCLICKS_PER_ROTATION/2) + wheel_delta += WHEELCLICKS_PER_ROTATION; /* Forward wrapping case */ + else if (wheel_delta > WHEELCLICKS_PER_ROTATION/2) + wheel_delta -= WHEELCLICKS_PER_ROTATION; /* Backward wrapping case */ + + /* Getting direction and wheel_keycode from wheel_delta. + * Need at least some clicks to be sure to avoid haptic fuzziness */ + if (wheel_delta >= WHEEL_SENSITIVITY) + wheel_keycode = BUTTON_SCROLL_FWD; + else if (wheel_delta <= -WHEEL_SENSITIVITY) + wheel_keycode = BUTTON_SCROLL_BACK; + else + wheel_keycode = BUTTON_NONE; + + if (wheel_keycode != BUTTON_NONE) + { + long v = (usec - last_wheel_usec) & 0x7fffffff; + + /* undo signedness */ + wheel_delta = (wheel_delta>0) ? wheel_delta : -wheel_delta; + + /* add the current wheel_delta */ + accumulated_wheel_delta += wheel_delta; + + v = v ? (1000000 * wheel_delta) / v : 0; /* clicks/sec = 1000000 * clicks/usec */ + v = (v * 360) / WHEELCLICKS_PER_ROTATION; /* conversion to degree/sec */ + v = (v<0) ? -v : v; /* undo signedness */ + + /* some velocity filtering to smooth things out */ + wheel_velocity = (31 * wheel_velocity + v) / 32; + /* limit to 24 bit */ + wheel_velocity = (wheel_velocity>0xffffff) ? 0xffffff : wheel_velocity; + + /* assume REPEAT = off */ + repeat = 0; + + /* direction reversals must nullify acceleration and accumulator */ + if (wheel_keycode != wheel_repeat) + { + wheel_repeat = wheel_keycode; + wheel_velocity = 0; + accumulated_wheel_delta = 0; + } + /* on same direction REPEAT is assumed when new click is within timeout */ + else if (TIME_BEFORE(usec, last_wheel_usec + WHEEL_REPEAT_TIMEOUT)) + { + repeat = BUTTON_REPEAT; + } + /* timeout nullifies acceleration and accumulator */ + if (TIME_AFTER(usec, last_wheel_usec + WHEEL_FAST_OFF_TIMEOUT)) + { + wheel_velocity = 0; + accumulated_wheel_delta = 0; + } + +#ifdef HAVE_WHEEL_POSITION + if (send_events) +#endif + /* The queue should have no other events when scrolling */ + if (queue_empty(&button_queue)) + { + /* each WHEEL_SENSITIVITY clicks = scrolling 1 item */ + accumulated_wheel_delta /= WHEEL_SENSITIVITY; +#ifdef HAVE_SCROLLWHEEL + /* use data-format for HAVE_SCROLLWHEEL */ + /* always use acceleration mode (1<<31) */ + /* always set message post count to (1<<24) for iPod */ + /* this way the scrolling is always calculated from wheel_velocity */ + queue_post(&button_queue, wheel_keycode | repeat, + (1<<31) | (1 << 24) | wheel_velocity); + +#else + queue_post(&button_queue, wheel_keycode | repeat, + (accumulated_wheel_delta << 16) | new_wheel_value); +#endif + accumulated_wheel_delta = 0; + } + last_wheel_usec = usec; + old_wheel_value = new_wheel_value; + } + } + else + { + /* scrollwheel was touched for the first time after finger lifting */ + old_wheel_value = new_wheel_value; + wheel_is_touched = true; + } + } + else + { + /* In this case the finger was lifted from the scrollwheel. */ + wheel_is_touched = false; } - } else if (status == 0xffffffff) { + } + else if (status == 0xffffffff) + { opto_i2c_init(); } } -wheel_end: - - if ((inl(reg) & 0x8000000) != 0) { + if ((inl(reg) & 0x8000000) != 0) + { outl(0xffffffff, 0x7000c120); outl(0xffffffff, 0x7000c124); } /* Save the new absolute wheel position */ +#ifdef HAVE_WHEEL_POSITION wheel_position = whl; +#endif return btn; } @@ -200,14 +312,18 @@ void ipod_4g_button_int(void) void button_init_device(void) { opto_i2c_init(); + /* hold button - enable as input */ GPIOA_ENABLE |= 0x20; GPIOA_OUTPUT_EN &= ~0x20; + /* hold button - set interrupt levels */ GPIOA_INT_LEV = ~(GPIOA_INPUT_VAL & 0x20); GPIOA_INT_CLR = GPIOA_INT_STAT & 0x20; + /* enable interrupts */ GPIOA_INT_EN = 0x20; + /* unmask interrupt */ CPU_INT_EN = 0x40000000; CPU_HI_INT_EN = I2C_MASK; diff --git a/firmware/target/arm/sandisk/sansa-e200/button-e200.c b/firmware/target/arm/sandisk/sansa-e200/button-e200.c index 5988e165a7..a74d8607c3 100644 --- a/firmware/target/arm/sandisk/sansa-e200/button-e200.c +++ b/firmware/target/arm/sandisk/sansa-e200/button-e200.c @@ -27,6 +27,7 @@ #define WHEEL_REPEAT_INTERVAL 300000 #define WHEEL_FAST_ON_INTERVAL 20000 #define WHEEL_FAST_OFF_INTERVAL 60000 +#define WHEELCLICKS_PER_ROTATION 48 /* wheelclicks per full rotation */ /* Clickwheel */ #ifndef BOOTLOADER @@ -137,10 +138,8 @@ void clickwheel_int(void) 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; + v = (v>0) ? 1000000 / v : 0; /* clicks/sec = 1000000 * clicks/usec */ + v = (v>0xffffff) ? 0xffffff : v; /* limit to 24 bit */ /* some velocity filtering to smooth things out */ wheel_velocity = (7*wheel_velocity + v) / 8; @@ -173,12 +172,12 @@ void clickwheel_int(void) else { /* fast ON gets filtered to avoid inadvertent jumps to fast mode */ - if (repeat && wheel_velocity > 0xffffff/WHEEL_FAST_ON_INTERVAL) + if (repeat && wheel_velocity > 1000000/WHEEL_FAST_ON_INTERVAL) { /* moving into fast mode */ wheel_fast_mode = 1 << 31; wheel_click_count = 0; - wheel_velocity = 0xffffff/WHEEL_FAST_OFF_INTERVAL; + wheel_velocity = 1000000/WHEEL_FAST_OFF_INTERVAL; } else if (++wheel_click_count < 2) { @@ -190,7 +189,7 @@ void clickwheel_int(void) } if (TIME_AFTER(current_tick, next_backlight_on) || - v <= 0xffffff/(1000000/4)) + v <= 4) { /* poke backlight to turn it on or maintain it no more often than every 1/4 second*/ @@ -214,7 +213,7 @@ void clickwheel_int(void) if (queue_empty(&button_queue)) { queue_post(&button_queue, btn, wheel_fast_mode | - (wheel_delta << 24) | wheel_velocity); + (wheel_delta << 24) | wheel_velocity*360/WHEELCLICKS_PER_ROTATION); /* message posted - reset delta */ wheel_delta = 1; }