nRF connect SDK:ボタンとLED

#ユーザーインターフェイスの基本
入力インターフェイスの代表であるボタンと出力インファーフェイスの代表であるLEDは組み込み機器では避けては通れない二つです。ユーザーインターフェイスとしては基本中の基本で、ボタンを押したらLEDが光るという、いわゆるLチカから入るのが王道ってものです(笑)。
#便利なコンフィギュレーション
nRF Connect SDKでは評価ボード用に便利なコンフィギュレーションが用意されています。

```prj.conf
# Enable DK LED and Buttons library
CONFIG_DK_LIBRARY=y
```

このコンフィギュレーションを有効にすることで評価ボードのボタンやLEDを簡単に扱うことができるようになります。もちろん、評価ボード以外で使う場合もDTSの記述さえ修正すれば流用することが可能です。
##同時押しも判定できる！
加えてnRF SDKではどれか一つのボタンしか検出できませんでしたが、nRF connect SDKで複数ボタンの同時押しを検出できるようになっています。加えて、同時押しをしたボタンのどれか一つだけを離した、というのも判定できるようになっています。
これは**進化**していますね！(笑)

```main.c
/*
 * Copyright (c) 2012-2014 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr.h>
#include <sys/printk.h>

#include <dk_buttons_and_leds.h>

void button_changed(uint32_t button_state, uint32_t has_changed)
{
    if (has_changed & 0x01)
    {
        if (button_state & has_changed)
        {
            printk("button 0 changes to on\n");
            dk_set_led_on(0);
        }
        else
        {
            printk("button 0 changes to off.\n");
            dk_set_led_off(0);
        }
    }

    if (has_changed & 0x02)
    {
        if (button_state & has_changed)
        {
            printk("button 1 changes to on\n");
            dk_set_led_on(1);
        }
        else
        {
            printk("button 1 changes to off.\n");
            dk_set_led_off(1);
        }
    }

    if (has_changed & 0x04)
    {
        if (button_state & has_changed)
        {
            printk("button 2 changes to on\n");
            dk_set_led_on(2);
        }
        else
        {
            printk("button 2 changes to off.\n");
            dk_set_led_off(2);
        }
    }

    if (has_changed & 0x08)
    {
        if (button_state & has_changed)
        {
            printk("button 3 changes to on\n");
            dk_set_led_on(3);
        }
        else
        {
            printk("button 3 changes to off.\n");
            dk_set_led_off(3);
        }
    }
}

void main(void)
{
    int err = 0;

    err = dk_buttons_init(button_changed);
    if (err) {
        printk("Cannot init buttons (err: %d)\n", err);
    }

    err = dk_leds_init();
    if (err) {
        printk("Cannot init LEDs (err: %d)\n", err);
    }
}
```

##あれ、何かがたりない・・・？
ここまで進めてきて何かが足りない、そんな気持ちになりませんか？(ならない)
そうです、nRF Connect SDKのライブラリの場合、nRF SDKと違ってボタンはONとOFFの二値しか取得することができません。えっ、それ以外に何があるのかって・・・？
ずばり、長押しです。nRF SDKは標準では使えませんでしたが長押しを判定することもできました。ただ、あまり使い勝手がよくなくてイベントハンドラー内で小細工は必要でしたが(笑)。
##しょうがない、自分で実装するか・・・
nRF connect SDKで長押しを実装する場合にパッと思いつくのは、ボタンを押したらディレイスレッドを起動して、ディレイが満了する前に放してしまったらスレッドをキャンセルするという方法でしょうか。いや、こういう記述っていかにもRTOSのマルチスレッド的な記述でワクワクしますね(笑)

```main.c
/*
 * Copyright (c) 2012-2014 Wind River Systems, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <zephyr.h>
#include <sys/printk.h>

#include <dk_buttons_and_leds.h>

static struct k_work_q user_queue;

K_THREAD_STACK_DEFINE(user_stack_area, 512);

static void button_holding_handler(struct k_work *work)
{
    printk("Button Holding!\n");
}
K_WORK_DELAYABLE_DEFINE(work_button_holding, button_holding_handler);

void button_changed(uint32_t button_state, uint32_t has_changed)
{
    if (has_changed & 0x01)
    {
        if (button_state & has_changed)
        {
            printk("button 0 changes to on\n");
            dk_set_led_on(0);
            k_work_schedule_for_queue(&user_queue, &work_button_holding, K_MSEC(2000));
        }
        else
        {
            printk("button 0 changes to off.\n");
            dk_set_led_off(0);
            k_work_cancel_delayable(&work_button_holding);
        }
    }

    if (has_changed & 0x02)
    {
        if (button_state & has_changed)
        {
            printk("button 1 changes to on\n");
            dk_set_led_on(1);
        }
        else
        {
            printk("button 1 changes to off.\n");
            dk_set_led_off(1);
        }
    }

    if (has_changed & 0x04)
    {
        if (button_state & has_changed)
        {
            printk("button 2 changes to on\n");
            dk_set_led_on(2);
        }
        else
        {
            printk("button 2 changes to off.\n");
            dk_set_led_off(2);
        }
    }

    if (has_changed & 0x08)
    {
        if (button_state & has_changed)
        {
            printk("button 3 changes to on\n");
            dk_set_led_on(3);
        }
        else
        {
            printk("button 3 changes to off.\n");
            dk_set_led_off(3);
        }
    }
}

void main(void)
{
    int err = 0;

    err = dk_buttons_init(button_changed);
    if (err)
    {
        printk("Cannot init buttons (err: %d)\n", err);
    }

    err = dk_leds_init();
    if (err)
    {
        printk("Cannot init LEDs (err: %d)\n", err);
    }

    k_work_queue_start(&user_queue, user_stack_area, K_THREAD_STACK_SIZEOF(user_stack_area), K_PRIO_PREEMPT(7), NULL);
}
```

同じことを4つ書いてもしょうがないので長押しを実装しているのはBUTTON1だけですが、スレッドを4つ起こすように記述することで全てのボタンで長押しを実装することができます。
ユーザーキューを作っているのはシステムキューで何か長い処理が実行されていた場合にボタン長押しのスレッドが待たされて2秒以上押していないといけなくなるのを防ぐためです。すべてのボタンに長押しを実装する場合も同様で、ユーザーキューを4つ分生成することをお勧めします。他のスレッドの処理待ちになると動作がおかしくなってしまう処理の場合は原則として専用キューが必要です。
##実際に動かしてみると
こんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/340602/a8443718-fb8b-32b5-fba9-def7c07e8b7c.png)