This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Error 19 [NRF_ERROR_RESOURCES] on modified HID Keyboard Example - from call to ble_hids_inp_rep_send

Hello,

I'm trying to add a rotary encoder support to the HID keyboard example on SDK17.0 with some custom hardware utilizing the NRF52840. It works well to send the requested characters per click-turn, but at some point it inevitably fails with Error 19, as if I were trying to send too many consecutive characters. It's a bit hard to tell, but it could be when I attempt to turn the encoder too fast? I have a really aggressive low-pass filter on there right now, and I've confirmed on the oscilloscope that I'm not getting any double-edges.

I have each encoder pin set up with a low-power GPIOTE event, each pointing to the same handler.

nrf_drv_gpiote_in_config_t in_config = GPIOTE_CONFIG_IN_SENSE_TOGGLE(false);
in_config.sense = NRF_GPIOTE_POLARITY_HITOLO;

err_code = nrf_drv_gpiote_in_init(enc_pins[0], &in_config, encoder_handler);
APP_ERROR_CHECK(err_code);
nrf_drv_gpiote_in_event_enable(enc_pins[0], true);

err_code = nrf_drv_gpiote_in_init(enc_pins[1], &in_config, encoder_handler);
APP_ERROR_CHECK(err_code);
nrf_drv_gpiote_in_event_enable(enc_pins[1], true);

And the handler. The error occurs on the first APP_ERROR_CHECK, line 18

void encoder_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action){
  ret_code_t err_code;

  //NRF_LOG_INFO("Enc handler");
  uint8_t data[] = {0, 0, 0, 0, 0, 0, 0, 0};
  
  if(nrf_gpio_pin_read(enc_pins[0]) != nrf_gpio_pin_read(enc_pins[1])){
    if(pin == enc_pins[1]){
      data[0] = enc_mods[1];
      data[2] = enc_keys[1];
    } else{
      data[0] = enc_mods[0];
      data[2] = enc_keys[0];
    }

    if(!m_in_boot_mode){
      err_code = ble_hids_inp_rep_send(&m_hids, INPUT_REPORT_KEYS_INDEX,  INPUT_REPORT_KEYS_MAX_LEN,  data,  m_conn_handle);    
      APP_ERROR_CHECK(err_code);
      data[0] = 0;
      data[2] = 0;
      err_code = ble_hids_inp_rep_send(&m_hids, INPUT_REPORT_KEYS_INDEX,  INPUT_REPORT_KEYS_MAX_LEN,  data,  m_conn_handle);
    } else{
      err_code = ble_hids_boot_kb_inp_rep_send(&m_hids, INPUT_REPORT_KEYS_MAX_LEN, data, m_conn_handle);
      APP_ERROR_CHECK(err_code);
      data[0] = 0;
      data[2] = 0;
      err_code = ble_hids_boot_kb_inp_rep_send(&m_hids, INPUT_REPORT_KEYS_MAX_LEN, data, m_conn_handle);
    }
    APP_ERROR_CHECK(err_code);
  }
}

Any help is appreciated!

Parents
  • Hi 

    This sounds like an issue caused by overloading the send buffers, yes. 

    Have you done any measurements to try and figure out how often the encoder callback occurs?

    An old trick is to toggle a pin in the function, and check on a scope how often it happens. 

    Alternatively you can add some log messages, if it doesn't happen hundreds of times every second. 

    Best regards
    Torbjørn

  • Thanks for the response. Nice trick with the pin toggle, especially useful as the oscilloscope will stop triggering after the failure and leave me with a capture of its dying breath - as pictured here:

    As the encoder handler is called twice per click-turn, and a character + release is only sent in the first of the two handler calls (when everything works properly), this image suggests that five reports were sent over ~20ms (character, release ... character, release ... character). Seeing as my connected device didn't continuously repeat the character, I'd actually revise to say that four reports were sent and the 5th caused Error 19.

    Now that I think about it, it's possible that the low-pass filter is so slow that the voltages may  not have settled by the 2nd call of the encoder handler, which I suppose could cause the handler to see inequality between enc_pins[0] and enc_pins[1] and thus send a 2nd character + release pair in a single turn event. So if I've got things straight, I've got at worst-case 8 reports sent over 20ms?

    I may try to lower the RC Time Constant anyway to avoid these issues, but the problem will still exist in the event that a user turns the knob very fast - is there a flag I can check to see the status of the send buffer? I removed the keypress queue functionality of the default HID Keyboard example, but perhaps I need to re-implement that if keypresses are going to come in this quickly. Ideally I suppose it'd be nice to expand the send buffer if possible too.

  • Hi

    One can do a lot of useful debugging with a scope ;)

    I think the best long term solution here is to decouple the hid updates from the encoder sampling, since as you say it is hard to predict how quickly the user will rotate the wheel. 

    There is little point trying to send data quicker than the Bluetooth connection interval, since this is what dictates the rate at which new packets can be sent (unless you buffer multiple packets, but that makes more sense for applications where you are sending larger amounts of data). 

    You can update some variable based on the encoder callback, and run some timer in the background that checks this variable and sends a new HID packet if the variable has changed since the last time. Here you can also check the status of other buttons that you might want to include in the packet. 

    And finally there is the question of whether or not you have looked into using the QDEC peripheral for your encoder sampling?

    Assuming you only need one scroll wheel the QDEC peripheral is designed for just this purpose, and allows you to reduce the amount of software interrupts that you need to process when analyzing the speed of a scroll wheel or motor. 

    Best regards
    Torbjørn

  • Torbjørn,

    Good idea to decouple the encoder and put it on a fixed-rate timer. I've done just that - in fact I utilized my existing timer that polled the normal keys and just had the encoder trigger that same process as well, incrementing a variable to keep track of how many characters need to be sent. I use a bool in tandem with this so that it can send a release on every other interval, otherwise quickly turning the knob would be interpreted as holding the key constantly. In case any other users come across this thread here and want a solution, here are the pertinent parts of mine:

    void encoder_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action){
      ret_code_t err_code;
    
      if(nrf_gpio_pin_read(enc_pins[0]) != nrf_gpio_pin_read(enc_pins[1])){
        poll_counter = 0;
        if(!polling){
         app_timer_start(c_switch_polling_timer_id, POLLING_RATE, NULL);  // start polling
         polling = true;
        }
    
        if(pin == enc_pins[1]){
          enc_accum[1]++;
        } else{
          enc_accum[0]++;
        }
      }
    }
    
    
    /************************************/
    /* within my polling timer handler: */
    /************************************/
    for(uint8_t i = 0; i < 2; i++){
        if(enc_accum[i]){
          poll_counter = 0;                           // keep "polling" while we deal with the buffered encoder inputs
          if(!enc_sendNow[i]){                        // at first, set this bool high and send characters now   
            enc_sendNow[i] = true;
            data_array[data_index++] = enc_keys[i];
            data_array[0] |= enc_mods[i];    
          } else{                                     // next polling period, decrement the buffer and set the bool false
            enc_accum[i]--;
            enc_sendNow[i] = false;
          }
        }
      }

    data_array[] is the HID report that gets sent every 25ms in my polling timer handler, as long as this `if((current_input != last_input) || enc_accum[0] || enc_accum[1])` condition is met.

    To answer your question, I have looked into QDEC but decided I'd like to implement my own functionality - not only to support multiple encoders, but also to be as low-power as possible. Didn't want to encourage the high-freq clock to run and eat up another few hundred uA's!

    Thanks for all your help!

Reply
  • Torbjørn,

    Good idea to decouple the encoder and put it on a fixed-rate timer. I've done just that - in fact I utilized my existing timer that polled the normal keys and just had the encoder trigger that same process as well, incrementing a variable to keep track of how many characters need to be sent. I use a bool in tandem with this so that it can send a release on every other interval, otherwise quickly turning the knob would be interpreted as holding the key constantly. In case any other users come across this thread here and want a solution, here are the pertinent parts of mine:

    void encoder_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action){
      ret_code_t err_code;
    
      if(nrf_gpio_pin_read(enc_pins[0]) != nrf_gpio_pin_read(enc_pins[1])){
        poll_counter = 0;
        if(!polling){
         app_timer_start(c_switch_polling_timer_id, POLLING_RATE, NULL);  // start polling
         polling = true;
        }
    
        if(pin == enc_pins[1]){
          enc_accum[1]++;
        } else{
          enc_accum[0]++;
        }
      }
    }
    
    
    /************************************/
    /* within my polling timer handler: */
    /************************************/
    for(uint8_t i = 0; i < 2; i++){
        if(enc_accum[i]){
          poll_counter = 0;                           // keep "polling" while we deal with the buffered encoder inputs
          if(!enc_sendNow[i]){                        // at first, set this bool high and send characters now   
            enc_sendNow[i] = true;
            data_array[data_index++] = enc_keys[i];
            data_array[0] |= enc_mods[i];    
          } else{                                     // next polling period, decrement the buffer and set the bool false
            enc_accum[i]--;
            enc_sendNow[i] = false;
          }
        }
      }

    data_array[] is the HID report that gets sent every 25ms in my polling timer handler, as long as this `if((current_input != last_input) || enc_accum[0] || enc_accum[1])` condition is met.

    To answer your question, I have looked into QDEC but decided I'd like to implement my own functionality - not only to support multiple encoders, but also to be as low-power as possible. Didn't want to encourage the high-freq clock to run and eat up another few hundred uA's!

    Thanks for all your help!

Children
Related