NRF_Desktop keyboard example with 4x4 keypad

Good day Everyone

Im trying to incorporate a 4x4 keypad matrix with the nrf_desktop Keyboard.conf example in Zephyr. I would like to send membrane keystrokes via USB and BLE as per original example. I have made the following changes to incorporate the keypad.

 activate CAF

CONFIG_CAF_BUTTONS_EVENT_LIMIT=16

developer.nordicsemi.com/.../buttons.html

In the Buttons_def.h I added my rows and coulombs

#include <caf/gpio_pins.h>

/* This configuration file is included only once from button module and holds
 * information about pins forming keyboard matrix.
 */

/* This structure enforces the header file is included only once in the build.
 * Violating this requirement triggers a multiple definition error at link time.
 */
const struct {} buttons_def_include_once;

static const struct gpio_pin col[] = {
	//4x4 keypad col GPIO (1,4 | 1,3 | 1,2 | 1,1) as per overlay file
	{ .port = 1, .pin = DT_GPIO_PIN(DT_NODELABEL(col1), gpios) },
	{ .port = 1, .pin = DT_GPIO_PIN(DT_NODELABEL(col2), gpios) },
	{ .port = 1, .pin = DT_GPIO_PIN(DT_NODELABEL(col3), gpios) },
	{ .port = 1, .pin = DT_GPIO_PIN(DT_NODELABEL(col4), gpios) },

};

static const struct gpio_pin row[] = {
	//dk buttons - original
	{ .port = 0, .pin = DT_GPIO_PIN(DT_NODELABEL(button0), gpios) },
	{ .port = 0, .pin = DT_GPIO_PIN(DT_NODELABEL(button1), gpios) },
	{ .port = 0, .pin = DT_GPIO_PIN(DT_NODELABEL(button2), gpios) },
	{ .port = 0, .pin = DT_GPIO_PIN(DT_NODELABEL(button3), gpios) },
	//4x4 keypad rows GPIO (1,8 | 1,7 | 1,6 | 1,5) as per overlay file
	{ .port = 1, .pin = DT_GPIO_PIN(DT_NODELABEL(row1), gpios) },
	{ .port = 1, .pin = DT_GPIO_PIN(DT_NODELABEL(row2), gpios) },
	{ .port = 1, .pin = DT_GPIO_PIN(DT_NODELABEL(row3), gpios) },
	{ .port = 1, .pin = DT_GPIO_PIN(DT_NODELABEL(row4), gpios) },

};

In the buttons_sim_def.h I have added my keystroke array.

/*
 * Copyright (c) 2020 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
 */

#include <caf/key_id.h>
#include "usb_hid_codes.h"
/* This configuration file is included only once from buttons_sim module
 * and holds information about generated button presses sequence.
 */

/* This structure enforces the header file is included only once in the build.
 * Violating this requirement triggers a multiple definition error at link time.
 */
const struct {} buttons_sim_def_include_once;

const static uint16_t simulated_key_sequence[] = {
	KEY_ID(0x00, 0x11), /* N */
	KEY_ID(0x00, 0x12), /* O */
	KEY_ID(0x00, 0x15), /* R */
	KEY_ID(0x00, 0x07), /* D */
	KEY_ID(0x00, 0x0C), /* I */
	KEY_ID(0x00, 0x06), /* C */
	KEY_ID(0x00, 0x2C), /* spacebar */
};

const static uint16_t membrane_key_stroke_to_send[] = {
	//below keys come from USB_HID_codes.h
    KEY_KP8,       
    KEY_KP2,      
    KEY_KP4,        
    KEY_KP6,       
    KEY_KP1,      
    KEY_KP7,
    KEY_KP3,
    KEY_KPSLASH,
    KEY_KPMINUS,
    KEY_KPPLUS,
    KEY_KPASTERISK,
    KEY_KPENTER,       
};

In the App.overlay I have added the rows and column pin configuration

{
	chosen {
		nordic,pm-ext-flash = &mx25r64;
	};
};

/ {
	chosen {
		/*
		 * In some default configurations within the nRF Connect SDK,
		 * e.g. on nRF52840 and nRF9160, the chosen zephyr,entropy node
		 * is &cryptocell. This devicetree overlay ensures that default
		 * is overridden wherever it is set, as this application uses
		 * the RNG node for entropy exclusively.
		 */
		zephyr,entropy = &rng;
	};

	rows {
        compatible = "gpio-leds";
        row1: row_1 {
            gpios = <&gpio1 8 GPIO_ACTIVE_HIGH>;
            label = "keypadrow1";
        };
        row2: row_2 {
            gpios = <&gpio1 7 GPIO_ACTIVE_HIGH>;
            label = "keypadrow2";
        };
       row3: row_3 {
            gpios = <&gpio1 6 GPIO_ACTIVE_HIGH>;
            label = "keypadrow3";
        };
       row4: row_4 {
            gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
            label = "keypadrow4";
        };
     };

	 col {
		compatible = "gpio-keys";
        col1: col_1 {
            gpios = <&gpio1 4 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
            label = "keypadcol1";
        };
        col2: col_2 {
            gpios = <&gpio1 3 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
            label = "keypadcol2";
        };
        col3: col_3 {
            gpios = <&gpio1 2 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
            label = "keypadcol3";
        };
        col4: col_4 {
            gpios = <&gpio1 1 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
            label = "keypadcol4";
        };
    };

	pwmleds1 {
		compatible = "pwm-leds";
		status = "okay";

		pwm_led1: led_pwm_1 {
			status = "okay";
			pwms = <&pwm1 0 PWM_MSEC(20) PWM_POLARITY_INVERTED>;
			label = "LED Conn State";
		};
	};

	pwmleds2 {
		compatible = "pwm-leds";
		status = "okay";

		pwm_led2: led_pwm_2 {
			status = "okay";
			pwms = <&pwm2 0 PWM_MSEC(20) PWM_POLARITY_INVERTED>;
			label = "LED Caps Lock";
		};
	};

	pwmleds3 {
		compatible = "pwm-leds";
		status = "okay";

		pwm_led3: led_pwm_3 {
			status = "okay";
			pwms = <&pwm3 0 PWM_MSEC(20) PWM_POLARITY_INVERTED>;
			label = "LED Num Lock";
		};
	};
};

&pwm0 {
	status = "okay";
	pinctrl-0 = <&pwm0_default_alt>;
	pinctrl-1 = <&pwm0_sleep_alt>;
	pinctrl-names = "default", "sleep";
};

&pwm1 {
	status = "okay";
	pinctrl-0 = <&pwm1_default_alt>;
	pinctrl-1 = <&pwm1_sleep_alt>;
	pinctrl-names = "default", "sleep";
};

&pwm2 {
	status = "okay";
	pinctrl-0 = <&pwm2_default_alt>;
	pinctrl-1 = <&pwm2_sleep_alt>;
	pinctrl-names = "default", "sleep";
};

&pwm3 {
	status = "okay";
	pinctrl-0 = <&pwm3_default_alt>;
	pinctrl-1 = <&pwm3_sleep_alt>;
	pinctrl-names = "default", "sleep";
};

&pwm_led0 {
	status = "okay";
	pwms = <&pwm0 0 PWM_MSEC(20) PWM_POLARITY_INVERTED>;
	label = "LED System State";
};

&qspi {
	status = "okay";
};

&pinctrl {
	pwm0_default_alt: pwm0_default_alt {
		group1 {
			psels = <NRF_PSEL(PWM_OUT0, 0, 13)>;
			nordic,invert;
		};
	};

	pwm0_sleep_alt: pwm0_sleep_alt {
		group1 {
			psels = <NRF_PSEL(PWM_OUT0, 0, 13)>;
			low-power-enable;
		};
	};

	pwm1_default_alt: pwm1_default_alt {
		group1 {
			psels = <NRF_PSEL(PWM_OUT0, 0, 14)>;
			nordic,invert;
		};
	};

	pwm1_sleep_alt: pwm1_sleep_alt {
		group1 {
			psels = <NRF_PSEL(PWM_OUT0, 0, 14)>;
			low-power-enable;
		};
	};

	pwm2_default_alt: pwm2_default_alt {
		group1 {
			psels = <NRF_PSEL(PWM_OUT0, 0, 15)>;
			nordic,invert;
		};
	};

	pwm2_sleep_alt: pwm2_sleep_alt {
		group1 {
			psels = <NRF_PSEL(PWM_OUT0, 0, 15)>;
			low-power-enable;
		};
	};

	pwm3_default_alt: pwm3_default_alt {
		group1 {
			psels = <NRF_PSEL(PWM_OUT0, 0, 16)>;
			nordic,invert;
		};
	};

	pwm3_sleep_alt: pwm3_sleep_alt {
		group1 {
			psels = <NRF_PSEL(PWM_OUT0, 0, 16)>;
			low-power-enable;
		};
	};
};
 

Im a bit stuck now as I need to now scan the matrix return the value from the array and send as per example to BLE and USB.

I was thinking of using a timer to scan the keypad every 150ms return. 

char keypad[ROWS][COLS] = {
    {'1', '2', '3', 'A'},
    {'4', '5', '6', 'B'},
    {'7', '8', '9', 'C'},
    {'*', '0', '#', 'D'}
};

char read_keypad(){

    for (int row_no = 0; row_no < 4; row_no++) {
      
	    gpio_pin_set_dt(&row1, row_no == 0 ? 0 : 1);
	    gpio_pin_set_dt(&row2, row_no == 1 ? 0 : 1);
        gpio_pin_set_dt(&row3, row_no == 2 ? 0 : 1);
		gpio_pin_set_dt(&row4, row_no == 3 ? 0 : 1);

		
		if (gpio_pin_get_dt(&col1)) return keypad[row_no][0];
		if (gpio_pin_get_dt(&col2)) return keypad[row_no][1];
        if (gpio_pin_get_dt(&col3)) return keypad[row_no][2];
        if (gpio_pin_get_dt(&col4)) return keypad[row_no][3];
    }
	
	return '\0';
}

from the timer

static void mytimer_cb(struct k_timer *dummy){

	char key = read_keypad();
	static uint8_t * p_key = keypadkey;

			if (key != '\0' ){
				 printk (" %c\n ", key);
				 
				switch (key){

                case '8': **send this somhow**(membrane_key_stroke_to_send);
                break;

                case '2' : **send this somhow**(membrane_key_stroke_to_send+1);
                break;

                case '4' : **send this somhow**(membrane_key_stroke_to_send+2);
                break;

                case '6' : **send this somhow**( membrane_key_stroke_to_send+3);
                break;

                case '1' : **send this somhow**(membrane_key_stroke_to_send+4);
                break;

                case '7' : **send this somhow**(membrane_key_stroke_to_send+5);
                break;

                case '3' : **send this somhow**(membrane_key_stroke_to_send+6);
                break;

                case 'A' : **send this somhow**(membrane_key_stroke_to_send+7);
                break;

                case 'B' : **send this somhow**(membrane_key_stroke_to_send+8);
                break;

                case 'C' : **send this somhow**(membrane_key_stroke_to_send+9);
                break;

                case '*' : **send this somhow**(membrane_key_stroke_to_send+10);
                break;
       
                case '#' : **send this somhow**(membrane_key_stroke_to_send+11);
                break;     
            }
					
	}	
}

If I look at https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/libraries/caf/caf_overview.html#c.button_event.key_id

It would seem the 4x4 keypad module could work out the box without me adding anything.  It may also be worth mentioning, that the 1st column on the keypad when pressed does result in the keys 'a', 'b' , 'c', d' being transmitted. Not sure where that comes from. the other columns do not respond. 

Thanks in advance as always

Parents
  • HI Kenneth thanks for that I did try that at first but that example does not seem to work nor is there a solution for it 

     RE: KSCAN sample build fail 

     RE: Adding a 3x3 Shield button board to nRF5340 DK 

    I would like to get the CAF working correctly . From documentation below.

    .. _nrf_desktop_buttons_sim:
    
    Button simulator module
    #######################
    
    .. contents::
       :local:
       :depth: 2
    
    Use the |button_sim| to generate the sequence of simulated key presses.
    The time between subsequent key presses is defined as a module configuration option.
    Generating keys can be started and stopped by pressing the predefined button.
    
    Module events
    *************
    
    .. include:: event_propagation.rst
        :start-after: table_buttons_sim_start
        :end-before: table_buttons_sim_end
    
    .. note::
        |nrf_desktop_module_event_note|
    
    Configuration
    *************
    
    To configure the |button_sim|:
    
    1. Enable and configure the :ref:`caf_buttons`.
       :c:struct:`button_event` is used to trigger the simulated button sequence.
    #. Enable the ``buttons_sim`` module by setting the :ref:`CONFIG_DESKTOP_BUTTONS_SIM_ENABLE <config_desktop_app_options>` Kconfig option.
    #. Define the output key ID sequence in the :file:`buttons_sim_def.h` file located in the board-specific directory in the :file:`configuration` directory.
       The mapping from the defined key ID to the HID report ID and usage ID is defined in :file:`hid_keymap_def.h` (this might be different for different boards).
    #. Define the interval between subsequent simulated button presses (:ref:`CONFIG_DESKTOP_BUTTONS_SIM_INTERVAL <config_desktop_app_options>`).
       One second is used by default.
    
    If you want the sequence to automatically restart after it ends, set :ref:`CONFIG_DESKTOP_BUTTONS_SIM_LOOP_FOREVER <config_desktop_app_options>`.
    By default, the sequence is generated only once.
    
    Implementation details
    **********************
    
    The |button_sim| generates button sequence using :c:struct:`k_work_delayable`, which resubmits itself.
    The work handler submits the press and the release of a single button from the sequence.
    
    Receiving :c:struct:`button_event` with the key ID set to :ref:`CONFIG_DESKTOP_BUTTONS_SIM_TRIGGER_KEY_ID <config_desktop_app_options>` either stops generating the sequence (in case it is already being generated) or starts generating it.
    

    I have moved my rows to the least significant bits P1.1-P1.4 and moved my COL to P1.7,P1.8,P1.10,P1.11 (p1.9 not avail on DK)

    row 1-4 (col1) seems to work and results in 'a','b','c','d'  but it does not scan col2, col3, col4. Is there another setting in kconfig I missed?

    according to the documentation after setting up the button_def.h all that is required  is to set the key_id in buttons_sim_def.h, how do one set this up

  • It's been long time since I looked at scan matrix, but the code looks straight forward enough. For testing you may try a small delay of 1us between setting the rows and reading the columns, just to make sure it is settled. I am slightly unsure if your if(column) should be if(column==0) instead, since you are checking if it' low? Any ways, if you are not able to find the problem, I suggest adding a logic analyzer (with 8 digital inputs) that can read the 4x4 and how the scanning occurs.

    Kenneth

  • Hi Kenneth I think we misunderstanding each other (most likely my bad as I see now I was unclear)

    The code posted right on top works fine im using it in other NRF applications my two questions are. 

    1. If I am to use the timer driven code to scan the matrix where is the best place to add it in the nrf_desktop example is it in  buttons_sim.c?

    2. If I am to use the CAF module  (much better solution ) the app event manager returns a key_id for each button in the keypad i.e 0x186,0x185,0x184 etc etc   I need to convert these to HID keystrokes is this from  buttons_sim_def.h? and is this also done through buttons_sim.c ?

Reply
  • Hi Kenneth I think we misunderstanding each other (most likely my bad as I see now I was unclear)

    The code posted right on top works fine im using it in other NRF applications my two questions are. 

    1. If I am to use the timer driven code to scan the matrix where is the best place to add it in the nrf_desktop example is it in  buttons_sim.c?

    2. If I am to use the CAF module  (much better solution ) the app event manager returns a key_id for each button in the keypad i.e 0x186,0x185,0x184 etc etc   I need to convert these to HID keystrokes is this from  buttons_sim_def.h? and is this also done through buttons_sim.c ?

Children
  • I think im getting somewhere here. The key_id is matched to the  HID code in the hid_keymap_def_keyboard.h file.

    currently my key_id comes out as

    0x04    0x84   0x104   0x184

    0x05    0x85   0x105   0x185

    0x06    0x86   0x106   0x186

    0x07    0x87   0x107   0x187

    I tried adding my  2nd,3rd,4th col to the hid_keymap but it looks like they need to follow upon the existing keymap in numerical order and I suspect there is going to be a limit. the error I get when I add my key_id

    ASSERTION FAIL [hid_keymap[i - 1].key_id < hid_keymap[i].key_id] @ ../src/modules/hid_state.c:1349
    The hid_keymap array must be sorted by key_id!

    is the higher value key_id  due to my pin usage?  

    or can I just add key_id in the keymap below in numerical order all the way up to 187?

    // third coloum is the HID keystroke 
    static const struct hid_keymap hid_keymap[] = {
    
    	{ KEY_ID(0x00, 0x00), 0x0004, REPORT_ID_KEYBOARD_KEYS }, /* A */
    	{ KEY_ID(0x00, 0x01), 0x0005, REPORT_ID_KEYBOARD_KEYS }, /* B */
    	{ KEY_ID(0x00, 0x02), 0x00E1, REPORT_ID_KEYBOARD_KEYS }, /* left shift */
    
    	{ KEY_ID(0x00, 0x04), 0x0007, REPORT_ID_KEYBOARD_KEYS }, /* A */
    	{ KEY_ID(0x00, 0x05), 0x0005, REPORT_ID_KEYBOARD_KEYS }, /* B */
    	{ KEY_ID(0x00, 0x06), 0x0006, REPORT_ID_KEYBOARD_KEYS }, /* C */
    	{ KEY_ID(0x00, 0x07), 0x0004, REPORT_ID_KEYBOARD_KEYS }, /* D */
    	{ KEY_ID(0x00, 0x08), 0x0008, REPORT_ID_KEYBOARD_KEYS }, /* E */
    	{ KEY_ID(0x00, 0x09), 0x0009, REPORT_ID_KEYBOARD_KEYS }, /* F */
    	{ KEY_ID(0x00, 0x0A), 0x000A, REPORT_ID_KEYBOARD_KEYS }, /* G */
    	{ KEY_ID(0x00, 0x0B), 0x000B, REPORT_ID_KEYBOARD_KEYS }, /* H */
    	{ KEY_ID(0x00, 0x0C), 0x000C, REPORT_ID_KEYBOARD_KEYS }, /* I */
    	{ KEY_ID(0x00, 0x0D), 0x000D, REPORT_ID_KEYBOARD_KEYS }, /* J */
    	{ KEY_ID(0x00, 0x0E), 0x000E, REPORT_ID_KEYBOARD_KEYS }, /* K */
    	{ KEY_ID(0x00, 0x0F), 0x000F, REPORT_ID_KEYBOARD_KEYS }, /* L */
    	{ KEY_ID(0x00, 0x10), 0x0010, REPORT_ID_KEYBOARD_KEYS }, /* M */
    	{ KEY_ID(0x00, 0x11), 0x0011, REPORT_ID_KEYBOARD_KEYS }, /* N */
    	{ KEY_ID(0x00, 0x12), 0x0012, REPORT_ID_KEYBOARD_KEYS }, /* O */
    	{ KEY_ID(0x00, 0x13), 0x0013, REPORT_ID_KEYBOARD_KEYS }, /* P */
    	{ KEY_ID(0x00, 0x14), 0x0014, REPORT_ID_KEYBOARD_KEYS }, /* Q */
    	{ KEY_ID(0x00, 0x15), 0x0015, REPORT_ID_KEYBOARD_KEYS }, /* R */
    	{ KEY_ID(0x00, 0x16), 0x0016, REPORT_ID_KEYBOARD_KEYS }, /* S */
    	{ KEY_ID(0x00, 0x17), 0x0017, REPORT_ID_KEYBOARD_KEYS }, /* T */
    	{ KEY_ID(0x00, 0x18), 0x0018, REPORT_ID_KEYBOARD_KEYS }, /* U */
    	{ KEY_ID(0x00, 0x19), 0x0019, REPORT_ID_KEYBOARD_KEYS }, /* V */
    	{ KEY_ID(0x00, 0x1A), 0x001A, REPORT_ID_KEYBOARD_KEYS }, /* W */
    	{ KEY_ID(0x00, 0x1B), 0x001B, REPORT_ID_KEYBOARD_KEYS }, /* X */
    	{ KEY_ID(0x00, 0x1C), 0x001C, REPORT_ID_KEYBOARD_KEYS }, /* Y */
    	{ KEY_ID(0x00, 0x1D), 0x001D, REPORT_ID_KEYBOARD_KEYS }, /* Z */
    
    	
    
    	{ KEY_ID(0x00, 0x2C), 0x002C, REPORT_ID_KEYBOARD_KEYS }, /* spacebar */
    };

  • I have managed to sort it out. :) but for completeness

    in hid_keymap_def_keyboard.h

    KEY_ID the first no is the COL , the 2nd no the ROW, the 3rd the value to be sent as defined in the hut1_12.pdf

    bear in mind the DK buttons take up row 0-3. so all rows need to start at 4

    	//col 1
    	{ KEY_ID(0x00, 0x04), 0x001E, REPORT_ID_KEYBOARD_KEYS }, 
    	{ KEY_ID(0x00, 0x05), 0x0021, REPORT_ID_KEYBOARD_KEYS }, 
    	{ KEY_ID(0x00, 0x06), 0x0024, REPORT_ID_KEYBOARD_KEYS },
    	{ KEY_ID(0x00, 0x07), 0x0024, REPORT_ID_KEYBOARD_KEYS }, 
    	
    	//col 2
    	{ KEY_ID(0x01, 0x04), 0x001E, REPORT_ID_KEYBOARD_KEYS }, 
    	{ KEY_ID(0x01, 0x05), 0x001F, REPORT_ID_KEYBOARD_KEYS }, 
    	{ KEY_ID(0x01, 0x06), 0x0020, REPORT_ID_KEYBOARD_KEYS }, 
    	{ KEY_ID(0x01, 0x07), 0x0021, REPORT_ID_KEYBOARD_KEYS }, 
    	//col3
    	{ KEY_ID(0x02, 0x04), 0x0022, REPORT_ID_KEYBOARD_KEYS }, 
    	{ KEY_ID(0x02, 0x05), 0x0023, REPORT_ID_KEYBOARD_KEYS }, 
    	{ KEY_ID(0x02, 0x06), 0x0024, REPORT_ID_KEYBOARD_KEYS },
    	{ KEY_ID(0x02, 0x07), 0x0025, REPORT_ID_KEYBOARD_KEYS }, 
    	//col 4
    	{ KEY_ID(0x03, 0x04), 0x0026, REPORT_ID_KEYBOARD_KEYS }, 
    	{ KEY_ID(0x03, 0x05), 0x0027, REPORT_ID_KEYBOARD_KEYS },
    	{ KEY_ID(0x03, 0x06), 0x0026, REPORT_ID_KEYBOARD_KEYS }, 
    	{ KEY_ID(0x03, 0x07), 0x0027, REPORT_ID_KEYBOARD_KEYS }, 

Related