Skip to content

Commit 2d400cd

Browse files
committed
drivers: input: vs1838b: Add support for VS1838B
The VS1838B is one of the most found infrared receiver found in electronic kits and is easy to setup with only a single GPIO used for signal transmission (apart from VCC and GND). This new driver let applications use the VS1838B as an input with events relayed as 0x0000<address><command>. Only the NEC protocol is supported in this version but more can be added later. Link: https://github-wiki-see.page/m/CoreELEC/remotes/wiki/08.-NEC-IR-Protocol-Datasheet This has been tested using the input_dump sample. Signed-off-by: Bastien JAUNY <[email protected]>
1 parent 1e1f944 commit 2d400cd

File tree

5 files changed

+380
-0
lines changed

5 files changed

+380
-0
lines changed

drivers/input/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ zephyr_library_sources_ifdef(CONFIG_INPUT_SBUS input_sbus.c)
3434
zephyr_library_sources_ifdef(CONFIG_INPUT_STM32_TSC_KEYS input_tsc_keys.c)
3535
zephyr_library_sources_ifdef(CONFIG_INPUT_STMPE811 input_stmpe811.c)
3636
zephyr_library_sources_ifdef(CONFIG_INPUT_TOUCH input_touch.c)
37+
zephyr_library_sources_ifdef(CONFIG_INPUT_VS1838B input_vs1838b.c)
3738
zephyr_library_sources_ifdef(CONFIG_INPUT_XEC_KBD input_xec_kbd.c)
3839
zephyr_library_sources_ifdef(CONFIG_INPUT_XPT2046 input_xpt2046.c)
3940
# zephyr-keep-sorted-stop

drivers/input/Kconfig

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ source "drivers/input/Kconfig.sdl"
3737
source "drivers/input/Kconfig.stmpe811"
3838
source "drivers/input/Kconfig.touch"
3939
source "drivers/input/Kconfig.tsc_keys"
40+
source "drivers/input/Kconfig.vs1838b"
4041
source "drivers/input/Kconfig.xec"
4142
source "drivers/input/Kconfig.xpt2046"
4243
# zephyr-keep-sorted-stop

drivers/input/Kconfig.vs1838b

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
config INPUT_VS1838B
4+
bool "Vishay VS1838B infrared receiver"
5+
default y
6+
depends on DT_HAS_VISHAY_VS1838B_ENABLED
7+
select GPIO
8+
help
9+
This option enabled the driver for the VS1838B infrared receiver.

drivers/input/input_vs1838b.c

+357
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*/
4+
5+
#define DT_DRV_COMPAT vishay_vs1838b
6+
7+
#include <zephyr/device.h>
8+
#include <zephyr/drivers/gpio.h>
9+
#include <zephyr/logging/log.h>
10+
#include <zephyr/input/input.h>
11+
#include <zephyr/kernel.h>
12+
13+
LOG_MODULE_REGISTER(input_vs1838b, CONFIG_INPUT_LOG_LEVEL);
14+
15+
/* A NEC packet is defined by:
16+
* - a lead burst (2 edges)
17+
* - an 8-bit address followed by its logical inverse
18+
* - an 8-bit command followed by its logical inverse
19+
* - a trailing burst
20+
*/
21+
22+
/* Constants used for parsing the edges buffer for NEC protocol */
23+
#define NEC_LEAD_PULSE_EDGE_OFFSET 0
24+
#define NEC_LEAD_PULSE_EDGE_WIDTH 2
25+
26+
#define NEC_ADDRESS_BYTE_EDGE_OFFSET ((NEC_LEAD_PULSE_EDGE_OFFSET) + (NEC_LEAD_PULSE_EDGE_WIDTH))
27+
#define NEC_ADDRESS_BYTE_EDGE_WIDTH (2 * (BITS_PER_BYTE))
28+
29+
#define NEC_REVERSE_ADDRESS_BYTE_EDGE_OFFSET \
30+
((NEC_ADDRESS_BYTE_EDGE_OFFSET) + (NEC_ADDRESS_BYTE_EDGE_WIDTH))
31+
#define NEC_REVERSE_ADDRESS_BYTE_EDGE_WIDTH (2 * (BITS_PER_BYTE))
32+
33+
#define NEC_COMMAND_BYTE_EDGE_OFFSET \
34+
((NEC_REVERSE_ADDRESS_BYTE_EDGE_OFFSET) + (NEC_REVERSE_ADDRESS_BYTE_EDGE_WIDTH))
35+
36+
#define NEC_COMMAND_BYTE_EDGE_WIDTH (2 * (BITS_PER_BYTE))
37+
#define NEC_REVERSE_COMMAND_BYTE_EDGE_OFFSET \
38+
((NEC_COMMAND_BYTE_EDGE_OFFSET) + (NEC_COMMAND_BYTE_EDGE_WIDTH))
39+
40+
#define NEC_REVERSE_COMMAND_BYTE_EDGE_WIDTH (2 * (BITS_PER_BYTE))
41+
42+
#define NEC_SINGLE_COMMAND_EDGES_COUNT \
43+
((NEC_REVERSE_COMMAND_BYTE_EDGE_OFFSET) + (NEC_REVERSE_COMMAND_BYTE_EDGE_WIDTH) + 2)
44+
45+
/* NEC protocol values */
46+
#define NEC_LEAD_PULSE_PERIOD_ON_USEC 9000
47+
#define NEC_LEAD_PULSE_PERIOD_OFF_USEC 4500
48+
#define NEC_BIT_DETECT_PERIOD_NSEC 562500
49+
#define NEC_BIT_DETECT_PERIOD_USEC ((NEC_BIT_DETECT_PERIOD_NSEC) / (NSEC_PER_USEC))
50+
#define NEC_BIT_0_TOTAL_PERIOD_USEC 1125
51+
#define NEC_BIT_1_TOTAL_PERIOD_USEC 2250
52+
/* Total delay between a command and a repeat code is 108ms
53+
* and total time of a command is 67.5ms
54+
*/
55+
#define NEC_TIMEOUT_REPEAT_CODE_MSEC (108 - 67)
56+
57+
/* Macros to define tick ranges based on a millisecond tolerance */
58+
#define VS1838B_MIN_TICK(usec, tol) \
59+
((((usec) - (tol)) * (CONFIG_SYS_CLOCK_TICKS_PER_SEC)) / USEC_PER_SEC)
60+
#define VS1838B_MAX_TICK(usec, tol) \
61+
((((usec) + (tol)) * (CONFIG_SYS_CLOCK_TICKS_PER_SEC)) / USEC_PER_SEC)
62+
63+
/* Empiric tolerance values. Might be a good idea to put them in the Kconfig? */
64+
#define VS1838B_NEC_LEAD_PULSE_PERIOD_TOLERANCE_USEC 400
65+
#define VS1838B_NEC_BIT_DETECT_PERIOD_TOLERANCE_USEC 150
66+
#define VS1838B_NEC_BIT_0_TOTAL_TOLERANCE_USEC 200
67+
#define VS1838B_NEC_BIT_1_TOTAL_TOLERANCE_USEC 200
68+
69+
/* Tick ranges for the NEC elements */
70+
#define VS1838B_NEC_LEAD_PULSE_ON_MIN_TICK \
71+
VS1838B_MIN_TICK(NEC_LEAD_PULSE_PERIOD_ON_USEC, \
72+
VS1838B_NEC_LEAD_PULSE_PERIOD_TOLERANCE_USEC)
73+
#define VS1838B_NEC_LEAD_PULSE_ON_MAX_TICK \
74+
VS1838B_MAX_TICK(NEC_LEAD_PULSE_PERIOD_ON_USEC, \
75+
VS1838B_NEC_LEAD_PULSE_PERIOD_TOLERANCE_USEC)
76+
77+
#define VS1838B_NEC_LEAD_PULSE_OFF_MIN_TICK \
78+
VS1838B_MIN_TICK(NEC_LEAD_PULSE_PERIOD_OFF_USEC, \
79+
VS1838B_NEC_LEAD_PULSE_PERIOD_TOLERANCE_USEC)
80+
#define VS1838B_NEC_LEAD_PULSE_OFF_MAX_TICK \
81+
VS1838B_MAX_TICK(NEC_LEAD_PULSE_PERIOD_OFF_USEC, \
82+
VS1838B_NEC_LEAD_PULSE_PERIOD_TOLERANCE_USEC)
83+
84+
#define VS1838B_NEC_BIT_DETECT_MIN_TICK \
85+
VS1838B_MIN_TICK(NEC_BIT_DETECT_PERIOD_USEC, VS1838B_NEC_BIT_DETECT_PERIOD_TOLERANCE_USEC)
86+
#define VS1838B_NEC_BIT_DETECT_MAX_TICK \
87+
VS1838B_MAX_TICK(NEC_BIT_DETECT_PERIOD_USEC, VS1838B_NEC_BIT_DETECT_PERIOD_TOLERANCE_USEC)
88+
89+
#define VS1838B_NEC_BIT_0_TOTAL_MIN_TICK \
90+
VS1838B_MIN_TICK(NEC_BIT_0_TOTAL_PERIOD_USEC, VS1838B_NEC_BIT_0_TOTAL_TOLERANCE_USEC)
91+
#define VS1838B_NEC_BIT_0_TOTAL_MAX_TICK \
92+
VS1838B_MAX_TICK(NEC_BIT_0_TOTAL_PERIOD_USEC, VS1838B_NEC_BIT_0_TOTAL_TOLERANCE_USEC)
93+
94+
#define VS1838B_NEC_BIT_1_TOTAL_MIN_TICK \
95+
VS1838B_MIN_TICK(NEC_BIT_1_TOTAL_PERIOD_USEC, VS1838B_NEC_BIT_1_TOTAL_TOLERANCE_USEC)
96+
#define VS1838B_NEC_BIT_1_TOTAL_MAX_TICK \
97+
VS1838B_MAX_TICK(NEC_BIT_1_TOTAL_PERIOD_USEC, VS1838B_NEC_BIT_1_TOTAL_TOLERANCE_USEC)
98+
99+
struct vs1838b_data {
100+
struct device const *dev;
101+
struct gpio_callback input_cb;
102+
struct k_work_delayable decode_work;
103+
int64_t edges_ticks[NEC_SINGLE_COMMAND_EDGES_COUNT];
104+
uint8_t edges_count;
105+
struct k_sem decode_sem;
106+
};
107+
108+
struct vs1838b_config {
109+
struct gpio_dt_spec input;
110+
};
111+
112+
/******************************************************************************
113+
* Internal stuff
114+
*******************************************************************************/
115+
static inline bool is_within_range(k_ticks_t const ticks, k_ticks_t const min, k_ticks_t const max)
116+
{
117+
return ((ticks <= max) && (ticks >= min));
118+
}
119+
120+
static uint8_t read_byte_from(int64_t *const edges_ticks, uint8_t const offset)
121+
{
122+
uint8_t byte = 0;
123+
/* Bytes are transmitted LSB first */
124+
k_ticks_t ticks_on = 0;
125+
k_ticks_t ticks_total = 0;
126+
for (uint8_t i = 0; i < BITS_PER_BYTE; ++i) {
127+
/*
128+
* To detect bits and their values we analyze:
129+
* - the initial pulse width
130+
* - the total period
131+
*/
132+
ticks_on = edges_ticks[(2 * i) + offset + 1] - edges_ticks[(2 * i) + offset];
133+
ticks_total = edges_ticks[(2 * i) + offset + 2] - edges_ticks[(2 * i) + offset];
134+
135+
LOG_DBG("ticks_on %lld", ticks_on);
136+
LOG_DBG("ticks_total %lld", ticks_total);
137+
if (is_within_range(ticks_on, VS1838B_NEC_BIT_DETECT_MIN_TICK,
138+
VS1838B_NEC_BIT_DETECT_MAX_TICK)) {
139+
if (is_within_range(ticks_total, VS1838B_NEC_BIT_0_TOTAL_MIN_TICK,
140+
VS1838B_NEC_BIT_0_TOTAL_MAX_TICK)) {
141+
// 0 detected
142+
} else if (is_within_range(ticks_total, VS1838B_NEC_BIT_1_TOTAL_MIN_TICK,
143+
VS1838B_NEC_BIT_1_TOTAL_MAX_TICK)) {
144+
// 1 detected
145+
byte += (1 << i);
146+
} else {
147+
LOG_WRN("Failed to identify detected bit at position %u", i);
148+
return false;
149+
}
150+
} else {
151+
LOG_WRN("Failed to detect a valid bit at position %u", i);
152+
return false;
153+
}
154+
}
155+
156+
return byte;
157+
}
158+
159+
static bool detect_leading_burst(int64_t *const edges_ticks)
160+
{
161+
/* Detect leading pulse using the first 3 edges */
162+
int64_t lead_ticks_on = edges_ticks[NEC_LEAD_PULSE_EDGE_OFFSET + 1] -
163+
edges_ticks[NEC_LEAD_PULSE_EDGE_OFFSET];
164+
int64_t lead_ticks_off = edges_ticks[NEC_LEAD_PULSE_EDGE_OFFSET + 2] -
165+
edges_ticks[NEC_LEAD_PULSE_EDGE_OFFSET + 1];
166+
167+
/* Manage the corner case of an overflow */
168+
if ((lead_ticks_on < 0) || (lead_ticks_off < 0)) {
169+
LOG_ERR("%s: Uptime ticks overflowed, frame is dropped", __FUNCTION__);
170+
LOG_ERR("%lld - %lld - %lld", edges_ticks[NEC_LEAD_PULSE_EDGE_OFFSET],
171+
edges_ticks[NEC_LEAD_PULSE_EDGE_OFFSET + 1],
172+
edges_ticks[NEC_LEAD_PULSE_EDGE_OFFSET + 2]);
173+
return false;
174+
}
175+
176+
LOG_DBG("Read %lld ticks on and %lld ticks off", lead_ticks_on, lead_ticks_off);
177+
178+
return is_within_range(lead_ticks_on, VS1838B_NEC_LEAD_PULSE_ON_MIN_TICK,
179+
VS1838B_NEC_LEAD_PULSE_ON_MAX_TICK) &&
180+
is_within_range(lead_ticks_off, VS1838B_NEC_LEAD_PULSE_OFF_MIN_TICK,
181+
VS1838B_NEC_LEAD_PULSE_OFF_MAX_TICK);
182+
}
183+
184+
static bool read_redundant_byte(int64_t *const edges_ticks, uint8_t *const byte,
185+
uint32_t const offset)
186+
{
187+
uint8_t temp_byte = read_byte_from(edges_ticks, offset);
188+
uint8_t reverse_byte = read_byte_from(edges_ticks, offset + (2 * BITS_PER_BYTE));
189+
190+
if (temp_byte == (uint8_t)(~reverse_byte)) {
191+
*byte = temp_byte;
192+
} else {
193+
LOG_ERR("Error while verifying the decoded byte");
194+
return false;
195+
}
196+
197+
return true;
198+
}
199+
200+
static bool read_address_byte(int64_t *const edges_ticks, uint8_t *const address)
201+
{
202+
return read_redundant_byte(edges_ticks, address, NEC_ADDRESS_BYTE_EDGE_OFFSET);
203+
}
204+
205+
static bool read_command_byte(int64_t *const edges_ticks, uint8_t *const command)
206+
{
207+
return read_redundant_byte(edges_ticks, command, NEC_COMMAND_BYTE_EDGE_OFFSET);
208+
}
209+
210+
static bool detect_last_burst(int64_t *const edges_ticks)
211+
{
212+
/* Detect leading pulse using the last 3 edges */
213+
int64_t burst_length = edges_ticks[NEC_SINGLE_COMMAND_EDGES_COUNT - 1] -
214+
edges_ticks[NEC_SINGLE_COMMAND_EDGES_COUNT - 2];
215+
216+
/* Manage the corner case of an overflow */
217+
if (burst_length < 0) {
218+
LOG_ERR("%s: Uptime ticks overflowed, frame is dropped", __FUNCTION__);
219+
return false;
220+
}
221+
222+
LOG_DBG("Read %lld ticks in the last burst", burst_length);
223+
224+
return is_within_range(burst_length, VS1838B_NEC_BIT_DETECT_MIN_TICK,
225+
VS1838B_NEC_BIT_DETECT_MAX_TICK);
226+
}
227+
228+
/*
229+
* Management of the decoding
230+
*/
231+
static void vs1838b_decode_work_handler(struct k_work *item)
232+
{
233+
struct k_work_delayable *dwork = k_work_delayable_from_work(item);
234+
struct vs1838b_data *data = CONTAINER_OF(dwork, struct vs1838b_data, decode_work);
235+
236+
if (k_sem_take(&(data->decode_sem), K_FOREVER) == 0) {
237+
if (detect_leading_burst(data->edges_ticks)) {
238+
LOG_DBG("Lead burst detected");
239+
240+
uint8_t address_byte = 0;
241+
if (read_address_byte(data->edges_ticks, &address_byte)) {
242+
LOG_DBG("Address decoded: 0x%X", address_byte);
243+
uint8_t command_byte = 0;
244+
if (read_command_byte(data->edges_ticks, &command_byte)) {
245+
LOG_DBG("Command decoded: 0x%X", command_byte);
246+
if (detect_last_burst(data->edges_ticks)) {
247+
LOG_DBG("Packet fully decoded");
248+
if (input_report(data->dev, INPUT_EV_DEVICE,
249+
INPUT_MSC_SCAN,
250+
(address_byte << 8) | command_byte,
251+
true, K_FOREVER) < 0) {
252+
LOG_ERR("Message failed to be enqueued");
253+
}
254+
}
255+
}
256+
}
257+
} else {
258+
LOG_DBG("No lead detected");
259+
}
260+
}
261+
262+
/* Reset the record */
263+
data->edges_count = 0;
264+
k_sem_give(&(data->decode_sem));
265+
}
266+
267+
/*
268+
* Internal callback
269+
*/
270+
static void vs1838b_input_callback(struct device const *dev, struct gpio_callback *cb,
271+
uint32_t pins)
272+
{
273+
/*
274+
* We want to:
275+
* - register the timestamps of interrupts
276+
* - try and decode the received bits when we reach the appropriate threshold
277+
*/
278+
int64_t const tick = k_uptime_ticks();
279+
struct vs1838b_data *data = CONTAINER_OF(cb, struct vs1838b_data, input_cb);
280+
281+
/* If we already schedule a decode, we need to cancel it. */
282+
if (k_work_cancel_delayable(&(data->decode_work)) != 0) {
283+
LOG_WRN("Decoding not cancelled!");
284+
}
285+
286+
if (k_sem_take(&(data->decode_sem), K_NO_WAIT) != 0) {
287+
/* Decoding might be pending */
288+
return;
289+
}
290+
291+
/* If more interrupts are received, they're likely to be repeat codes
292+
* and we choose to ignore them.
293+
*/
294+
if (data->edges_count < NEC_SINGLE_COMMAND_EDGES_COUNT) {
295+
data->edges_ticks[data->edges_count++] = tick;
296+
}
297+
298+
if (data->edges_count == NEC_SINGLE_COMMAND_EDGES_COUNT) {
299+
/* There's a candidate!
300+
* If nothing gets in during the grace period
301+
* it *should* be an entire command.
302+
*/
303+
k_work_schedule(&(data->decode_work), K_MSEC(NEC_TIMEOUT_REPEAT_CODE_MSEC));
304+
}
305+
k_sem_give(&(data->decode_sem));
306+
}
307+
308+
static int vs1838b_init(struct device const *dev)
309+
{
310+
struct vs1838b_config const *config = dev->config;
311+
struct gpio_dt_spec const *data_input = &(config->input);
312+
struct vs1838b_data *data = dev->data;
313+
314+
data->dev = dev;
315+
316+
if (!gpio_is_ready_dt(data_input)) {
317+
LOG_ERR("GPIO input pin is not ready");
318+
return -ENODEV;
319+
}
320+
321+
/*
322+
* Setup the input as an interrupt source
323+
* and register an associated callback.
324+
*/
325+
gpio_pin_configure_dt(data_input, GPIO_INPUT);
326+
gpio_pin_interrupt_configure_dt(data_input, GPIO_INT_EDGE_BOTH);
327+
gpio_init_callback(&(data->input_cb), vs1838b_input_callback, BIT(data_input->pin));
328+
gpio_add_callback_dt(data_input, &(data->input_cb));
329+
330+
k_sem_init(&(data->decode_sem), 1, 1);
331+
k_work_init_delayable(&(data->decode_work), vs1838b_decode_work_handler);
332+
333+
return 0;
334+
}
335+
336+
/******************************************************************************
337+
* Multi-instance init stuff
338+
*******************************************************************************/
339+
#define VS1838B_DEFINE(inst) \
340+
static struct vs1838b_data vs1838b_data_##inst = { \
341+
.dev = NULL, \
342+
.input_cb = {}, \
343+
.decode_work = {}, \
344+
.edges_ticks = {}, \
345+
.edges_count = 0, \
346+
.decode_sem = {}, \
347+
}; \
348+
\
349+
static struct vs1838b_config const vs1838b_config_##inst = { \
350+
.input = GPIO_DT_SPEC_INST_GET(inst, data_gpios), \
351+
}; \
352+
\
353+
DEVICE_DT_INST_DEFINE(inst, vs1838b_init, NULL, &vs1838b_data_##inst, \
354+
&vs1838b_config_##inst, POST_KERNEL, CONFIG_INPUT_INIT_PRIORITY, \
355+
NULL);
356+
357+
DT_INST_FOREACH_STATUS_OKAY(VS1838B_DEFINE)
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
description: Vishay VS1838B infrared receiver
4+
5+
compatible: "vishay,vs1838b"
6+
7+
properties:
8+
data-gpios:
9+
required: true
10+
type: phandle-array
11+
description: |
12+
GPIO used to transmit the received data.

0 commit comments

Comments
 (0)