Post Reply 
HP 15c CE - New firmware update officially available!
08-07-2024, 09:16 AM
Post: #77
RE: HP 15c CE - New firmware update officially available!
I agree it seems like the debounce algorithm used on the HP 15C CE is at fault, and I have noticed double keypress issues on all ARM-based 15C and 12C calculators I've played with.

To debounce a key properly, there really needs to be a debounce counter similar to what brouhaha describes, and importantly debouncing needs to happen for both the key press and the key release:

Quote:
Code:

The per-key debounce algorithm is:
    1. if the key scan state == the current debounce state, reset debounce counter to 0, and return (no change)
    2. [key scan state != current debounce state] increment debounce counter
    3. if new debounce counter value < threshold, return (no change)
    4. Set the debounced state to the key scan state, set the counter back to zero, and return that the key has been pressed or released

The purpose of the counter is to act as sort of an "integrator" to filter any possible noise. This page describes the implementation of the debounce counter and why it's needed, along with a hybrid "quick-draw"/integrator approach to let the key press be registered immediately instead of waiting for the debounce count (as described by brouhaha).

Now, in comparison, a lot of simplistic debounce algorithms just wait a fixed amount of time (e.g. 100ms or 120ms) without any debounce counter (which needs to run at a higher speed, e.g. every 10ms as in brouhaha's 100Hz example) and without debouncing both the key press and release. The issue is this 100 or 120ms "debounce time" is much longer than necessary (which can lead to missed key strokes), and also doesn't actually fix the bouncing problem completely when key bounce on key release is ignored. We usually think about key bounce more on key press, but actually keys tend to physically bounce on both key press and key release.

The code for a very simple 4-function calculator that ran on the original AT91SAM ARM-based HP 12C (and the HP 15C LE) has been previously posted with a "dev kit" for the original AT91SAM ARM-based HP 12C, and the overall debounce algorithm seems to match what Dave Britten wrote, although the code details have probably changed quite a bit for the new ATSAM4LC2C ARM-based HP 12C and HP 15C CE (and also I think there were power consumption issues while keys were held down on the original AT91SAM calculators' firmware).

I was a little disappointed when I first read through the keyboard handling code to try to figure out why the original AT91SAM calculators had key bounce issues (although I didn't post any angry rants at the time...I think it's great that they released the 15C LE and CE, and that they released any code at all).

I've posted snippets of the code below (should still be publicly available, need to search for a link to where), with some non-relevant stuff edited out with (...), and the code highlighted as php code even though it is actually C/C++ code (because I think highlighting php is all that the forum supports).

The basic gist of it is initially the calculator is sleeping. When a key is pressed, a keyboard interrupt is triggered, and ScanKeyboard() gets called. The keyboard interrupt also starts a timer 0 interrupt every ~60ms (confusingly the interrupt service routine is named TimerInterupt2 instead of 0) which continues calling ScanKeyboard() every ~60ms while keys are pressed. Note that nothin so far is directly related to key debouncing, including the 60ms polling interval.

In the ScanKeyboard() function, the code tracks the current (previous) keys pressed in KeyboardMap and sees if any new keys are pressed. If a new key is pressed, and it's not equal to LastKeyPress, the ScanKeyboard() function registers the keypress (immediately) and also starts a one-shot timer 1 interrupt to fire after ~50ms and call the ResetLastKeyPress() function/interrupt handler. This is the full extent of the debouncing: the ResetLastKeyPress() function just clears LastKeyPress to -1, so that if a new key press is detected by ScanKeyboard() and it's same key as before it will again be registered.

Now here's an example where things go wrong. For the example I will use the following numbers:
  • 100ms as the debounce time (which is different from the 50ms in the code snippet)
  • 60ms as the polling time (which is the value from the code snippet, and probably not frequent enough, although that's not the main issue for the algorithm being considered here)
  • ~30ms as the actual physical key bounce time (which is a reasonable estimate and assumed to be symmetric for both key press and key release)

The sequence of events considered is the key is pressed and held down for 175ms and then released. This should only register 1 keypress, but actually registers 2:
  • t=0 key is pressed
  • KeyboardInterrupt() is called, which calls ScanKeyboard() and also disables keyboard interrupts and sets up a timer to continue calling ScanKeyboard() every 60ms while keys are pressed
  • ScanKeyboard() immediately registers the new keypress, and starts a one-shot timer to call ResetLastKeyPress() after 100ms (the value is ~50ms in the code snippet)
  • ScanKeyboard() also updates System.KeyboardMap, which is used to determine which keys that are pressed are new key presses on subsequent calls to ScanKeyboard()
  • ScanKeyboard() also updates System.LastKeyPress, which is used for debounce purposes
  • from t=0 to ~30ms we assume the key bounces...this doesn't matter though because the ScanKeyboard() function is only called to poll the keyboard every 60ms in the timer. Even if the timer were triggered more frequently than 60ms, System.LastKeyPress is currently set to the current bouncing key and a bounce will be detected (and ignored)
  • at t=~30ms we assume the key is physically completely in the "pressed" state and no longer bouncing
  • at t=~60ms, the polling timer fires (calls TimerInterupt2() in the code) to poll the keyboard by calling ScanKeyboard(), where no new key is detected
  • at t=~100ms, the other timer fires, which calls ResetLastKeyPress(), which clears System.LastKeyPress. It also disables this timer, making it a one-shot timer. (In the actual code, this is a 50ms timer, not a 100ms timer.)
  • at t=~120ms, the polling timer fires again to poll the keyboard by calling ScanKeyboard(), where no new key is detected
  • at t=175ms, we assume the user releases the key and it begins bouncing for ~30ms (from 175ms to ~205ms)
  • at t=~180ms, the polling timer fires again to poll the keyboard by calling ScanKeyboard(), and notices that no key is pressed (we assume that although the key is bouncing between pressed/not pressed, it happens to be not pressed here), so the interrupt service routine disables both timers (although in this example the one-shot timer is already disabled), and also re-enables keyboard interrupts. The call to ScanKeyboard() also updates System.KeyboardMap which tracks which keys are pressed.
  • at some time between ~180ms and ~205ms, since the key continues bouncing for ~30ms from key release (at 175ms), the key gets erroneously detected as a 2nd key press (which is then debounced properly for the remainder of the ~30ms period)

This main problem is the code doesn't seem to consider the possibility of the keys bouncing on key release, but this is a very common problem, and firmware needs to deal with it. There is also annoyingly a lot of luck involved to trigger the issue, since the press duration needs to be slightly less than some multiple of the polling timer duration. Most of the time, the issue doesn't happen, but annoyingly sometimes it does.

Increasing the debounce time to beyond how long most people take to quickly press and release a button plus the bounce time (instead of setting the debounce time to slightly more than the bounce time) does "solve" the issue at the expense of potentially missing fast consecutive actual button presses from the user.

Also as noted in the example above, when holding the button for longer than the debounce time (and then releasing), it is theoretically possible to trigger an unwanted double press. But since users don't typically repeatedly hold buttons for periods longer than 120ms, and the problem doesn't actually trigger that often, users are unlikely to see these double presses.

Keyboard interrupt
PHP Code:
static void KeyboardInterupt()
{
  
Printk("<Keyboard Interupt>");
  
// acknoledge interrupt if needed (even if not!)
  
unsigned int dummyAT91C_BASE_PIOC->PIO_ISRdummydummy;

  
// handle keyboard interupt
  
if (!ScanKeyboard()) // scan keyboard and add keys in buffer..
  
// if keys down...
    
DisableKeyboardInterupt(); // disable keyboard interrupt as
    
EnableTimerInterupt(060TimerInterupt2);
  } else
    
EnableKeyboardInterupt();  // if no keys down, then re-enable the keyboard interrupts...
}

...

/*! \fn static void EnableTimerInterupt(int timer, int time, void(*function)())
 *  @ingroup timerdrv
 *\brief  enable timer causing an interupt every time ms, calling the TimerInterupt function
    \param time specifies the time duration in milli seconds (actually, in 1024th of second, but who is counting), for which the timer has to be enabled.
 **/
void EnableTimerInterupt(int timerint timevoid(*function)())
... 

ScanKeyboard()
PHP Code:
/*! \fn ScanKeyboard
 *\brief  scans the keyboard
 * -#  update the key map
 * -#  update LastKeyPress if a new key was pressed
 * -#  debounce stuff
 * -#  and place stuff in the key buffer if it's a new key press..
 * -#  a key consumer program should call GetKey() to read a key from the buffer
 * -#  return true if no key is pressed..
 **/
static int ScanKeyboard()
{
  
unsigned long long NewKeyMap= ~0LL;

  
// read the 4 lines of the keyboard and create a 64 bit structure
  // 10 bit per line to hold the keyup/down information
  // 1 bit per key...
  
AT91C_BASE_PIOC->PIO_ODRAllKeyboardRows// set as inputs
  
unsigned int t;
  for (
int line=3line>=0line--)
  {
    ...(
NewKeyMap gets populated with pressed buttons)
  }
  
AT91C_BASE_PIOC->PIO_OER=  AllKeyboardRows// all outputs...
  // invert the result as we are working with inverted logic
  
NewKeyMap= ~NewKeyMap;
//  printkeymask(NewKeyMap);
  
if ((AT91C_BASE_PIOC->PIO_PDSR&(1<<10))==0NewKeyMap|= OnKeyInKeyMap// ON Key

  // new key press is the list of the keys that are pressed now, but were not
  // pressed last time...
  
unsigned long long NewKeyPressNewKeyMap & ~System.KeyboardMap;

  
// key released are the keys that are not pressed now, but were presssed last time!
  // in this case, we are not using that information because the software
  // does not do anything on key release... so it's just commented out
  // unsigned long long KeyReleased= KeyboardMap & ~NewKeyMap;

  // save the current key map as the key map
  
System.KeyboardMapNewKeyMap;

  ...

  
// is there a new key down?
  
if (NewKeyPress==0LL)
    return 
System.KeyboardMap==0LL// no, just return true if no key is down at all...
  
  // isolate the first new key down..
  
for (int r=0r<40r+=10)
    for (
int c=0c<10c++)
    {
      if (
NewKeyPress&1!=0// is that key down?
      
{
        if (
NewKeyPress>>1==0// if this is not the only new key down, chances are this is a ghost effect
                               // or the user is mighty good at pressing 2 keys exactly at the same time (give or take
                               // timer delay...) in all cases, if this is a double press, then we can not know which key was pressed first
                               // and should ignore it, and if it's a ghost, we can not know which key is actually pressed and
                               // should also ignore the key press...
        
// ok, so, this is a valid key press... but is it a bounce?
          
int keyr+c;
          if (
System.LastKeyPress!=key// is it a bounce? if yes, ignore key...
          
{
            
System.LastKeyPresskey// not a bounce, save the key for alter bounce detection!
            
AddKeyInBuffer(key);      // send key to the system
            
Printk("<Setup Timer 1 interupt>");
            
EnableTimerInterupt(150ResetLastKeyPress); // program timer for end of bounce time detection
          
}
        }
        return 
0;  // return, and there is at least one key down...
      
}
      
NewKeyPress>>=1// next key.. this is of course inefficient, but hey... good enough for now... and on an arm it's not too bad anyway...
    
}
  return 
0// put here to prevent a warning, but can not happen...


One-shot timer 1 interrupt (single-shot timer which clears LastKeyPress, disabling debounce detection)
PHP Code:
// called after x ms (max bounce time) to disable bounce checking on the last key pressed
static void ResetLastKeyPress()
{
  
Printk("<Timer 1 interupt>");
  
AcknoledgeTimerInterupt(1);
  
System.LastKeyPress= -1;
  
StopTimer(1);


Polling timer 0 interrupt (confusingly named TimerInterupt2() instead of 0, this interrupt continues polling while keys are pressed)
PHP Code:
static void TimerInterupt2()
{
  
Printk("<Timer 0 interupt>");
  
AcknoledgeTimerInterupt(0);

  if (
ScanKeyboard())
  {
    
Printk("<Stop Timer 1 interupt>");
    
StopTimer(0);
    
StopTimer(1);
    
EnableKeyboardInterupt();
  } else
    
EnableTimerInterupt(060TimerInterupt2);

Find all posts by this user
Quote this message in a reply
Post Reply 


Messages In This Thread
RE: HP 15c CE - New firmware update officially available! - jklsadf - 08-07-2024 09:16 AM



User(s) browsing this thread: 14 Guest(s)