Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

drivers: input: Implement driver for ADC keys #68446

Merged
merged 2 commits into from Feb 26, 2024

Conversation

xingrz
Copy link
Member

@xingrz xingrz commented Feb 2, 2024

This commit introduces a driver for ADC keys, a common circuit design where keys are connected to an ADC input via a resistor ladder.

The driver itself does not calculate each possible combination of resistor values. Instead, users are required to specify the voltage for each single key press or for combinations of key presses.

Example 1

The schematic of a common design:

image

An example DTS:

&adc1 {
	#address-cells = <1>;
	#size-cells = <0>;
	status = "okay";

	channel@4 {
		reg = <4>;
		zephyr,gain = "ADC_GAIN_1";
		zephyr,reference = "ADC_REF_INTERNAL";
		zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
		zephyr,resolution = <12>;
	};
};

/ {
	adc-keys {
		compatible = "adc-keys";
		io-channels = <&adc1 4>;
		sample-period-ms = <100>;
		keyup-threshold-mv = <0>;

		key_1 {
			press-thresholds-mv = <1650>;  /* K1 */
			zephyr,code = <INPUT_KEY_1>;
		};

		key_2 {
			press-thresholds-mv = <1357>;  /* K2 */
			zephyr,code = <INPUT_KEY_2>;
		};

		key_3 {
			press-thresholds-mv = <1211>;  /* K3 */
			zephyr,code = <INPUT_KEY_3>;
		};

		key_4 {
			press-thresholds-mv = <1058>;  /* K4 */
			zephyr,code = <INPUT_KEY_4>;
		};
	};
};

Example 2

Another example that supports simultaneously press both of keys:

image
/ {
	adc-keys {
		compatible = "adc-keys";
		io-channels = <&adc1 4>;
		sample-period-ms = <100>;
		keyup-threshold-mv = <0>;

		key_4 {
			press-thresholds-mv = <1650>,  /* K4 */
					      <2536>;  /* K4 + K5 */
			zephyr,code = <INPUT_KEY_4>;
		};

		key_5 {
			press-thresholds-mv = <2300>,  /* K5 */
					      <2536>;  /* K4 + K5 */
			zephyr,code = <INPUT_KEY_5>;
		};
	};
};

In the circuit above, for K4:

image

For K5:

image

For both K4 and K5 being pressed simultaneously:

image

@xingrz xingrz force-pushed the input-adc-keys branch 5 times, most recently from 21ee194 to 6db4b16 Compare February 2, 2024 04:43
@xingrz xingrz marked this pull request as ready for review February 2, 2024 04:58
@zephyrbot zephyrbot added area: Input Input Subsystem and Drivers area: Devicetree Binding PR modifies or adds a Device Tree binding labels Feb 2, 2024
adc_keys_process(dev);
}

static void adc_keys_timer_handler(struct k_timer *timer)
Copy link
Collaborator

@bbilas bbilas Feb 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could k_work_delayable be used instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Copy link
Member

@fabiobaltieri fabiobaltieri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add an instance of this in tests/drivers/build_all/input/app.overlay so it gets built in CI?

const struct device *self;
struct k_work_delayable dwork;
struct adc_sequence seq;
struct adc_keys_key_data *key_data;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pointer can go in the config structure since the pointer itself does not change, save few bytes of ram. Then at that point the only thing to initialize is self, you can set that in adc_keys_init and drop the static initialization, which saves few bytes of flash as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your suggestion. My understanding is that even if I assign data->self in adc_keys_init, the self pointer would still occupy some memory within struct adc_keys_data. The DEVICE_DT_INST_GET merely populates this pointer with an address at compile time. Am I correct in this understanding? It's a bit unexpected for me. Looking forward to your clarification.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's right, but if you initialize it statically then it's going to take the space in RAM and also space in flash for the static initialization, including for the members that you are not explicitly initializing (storing a bunch of 0 essentially), while if you let it uninitialized the system will zero-initialize it automatically and you just use the flash for the instructions to set the pointer, so it's usually a good idea to leave data structures uninitalized if possible.

dts/bindings/input/adc-keys.yaml Outdated Show resolved Hide resolved
@xingrz xingrz force-pushed the input-adc-keys branch 4 times, most recently from 1c36de9 to 32bd46c Compare February 2, 2024 16:38
io-channels = <&test_adc 0>;
keyup-threshold-mv = <0>;
button_0 {
press-thresholds-mv = <1500>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add an extra value and an extra button to make sure that the iteration macros are tested

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(still not addressed)

drivers/input/input_adc_keys.c Outdated Show resolved Hide resolved
drivers/input/input_adc_keys.c Outdated Show resolved Hide resolved
drivers/input/input_adc_keys.c Show resolved Hide resolved
* being overwritten by another threshold configuration.
*/
if (closest_mv == code_cfg->press_mv) {
key_data->curr_state = 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
key_data->curr_state = 1;
key_data->curr_state = true;

* Reset the state so that it can be updated in the next
* iteration.
*/
key_data->curr_state = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
key_data->curr_state = 0;
key_data->curr_state = false;

Comment on lines 198 to 201
#define ADC_KEYS_KEY_DATA(node_id) \
{ \
.zephyr_code = DT_PROP(node_id, zephyr_code), .last_state = 0, .curr_state = 0, \
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about moving zephyr_code in its own array? I think you could have a const uint16_t *codes for the codes, that is going to be key_cnt sized, then you only have to initialize that statically and you can leave key_data being zero initialized automatically. That will save another bit of flash and another bit of ram. :-)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea! Now everything looks comfortable.

io-channels = <&test_adc 0>;
keyup-threshold-mv = <0>;
button_0 {
press-thresholds-mv = <1500>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(still not addressed)

drivers/input/input_adc_keys.c Show resolved Hide resolved
Copy link
Member

@fabiobaltieri fabiobaltieri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, just few nitpicks on the types but looks good otherwise

drivers/input/input_adc_keys.c Outdated Show resolved Hide resolved
drivers/input/input_adc_keys.c Show resolved Hide resolved
drivers/input/input_adc_keys.c Show resolved Hide resolved
drivers/input/input_adc_keys.c Outdated Show resolved Hide resolved
drivers/input/input_adc_keys.c Show resolved Hide resolved
@xingrz xingrz force-pushed the input-adc-keys branch 2 times, most recently from 0de60a9 to 0602460 Compare February 15, 2024 13:57
fabiobaltieri
fabiobaltieri previously approved these changes Feb 16, 2024
Copy link
Member

@fabiobaltieri fabiobaltieri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good stuff thanks for contributing this.

data->self = dev;
k_work_init_delayable(&data->dwork, adc_keys_work_handler);

#if defined(CONFIG_INPUT_LOG_LEVEL_DBG)
Copy link
Collaborator

@bbilas bbilas Feb 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#if defined(CONFIG_INPUT_LOG_LEVEL_DBG)
if (IS_ENABLED((CONFIG_INPUT_LOG_LEVEL_DBG)) {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? It should be completely removed in compile time if not enabled

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linker should drop this part anyway. You can double-check that by comparing the binaries.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a bit too dependent on the compiler's optimization level. Wouldn't using the preprocessor be a more recommended approach, as discussed in #53200?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends. In that case, the compiler will catch the errors in that for loop even though CONFIG_INPUT_LOG_LEVEL_DBG is disabled. As it's an init function it does not have much impact on the code even though the linker would not drop it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that's enlightening. Thank you for your suggestion. Updated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for catching this @bbilas -- there's a bit in the coding style guide: https://docs.zephyrproject.org/latest/kernel/util/index.html#c.IS_ENABLED, yes it does depend on compiler optimization but that's a fairly common theme in the code base and at this point just something understood and utilized

This commit introduces a driver for ADC keys, a common circuit design where
keys are connected to an ADC input via a resistor ladder.

Signed-off-by: Chen Xingyu <hi@xingrz.me>
* so that it'll be built by the CI

Signed-off-by: Chen Xingyu <hi@xingrz.me>
@fabiobaltieri fabiobaltieri added this to the v3.7.0 milestone Feb 20, 2024
@fabiobaltieri fabiobaltieri merged commit eaa492a into zephyrproject-rtos:main Feb 26, 2024
18 checks passed
@xingrz
Copy link
Member Author

xingrz commented Feb 26, 2024

Thanks all! I've learned a lot from this PR.

@xingrz xingrz deleted the input-adc-keys branch February 26, 2024 13:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: Devicetree Binding PR modifies or adds a Device Tree binding area: Input Input Subsystem and Drivers
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants