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

modules: led: Add PWM LED effects handling #235

Merged
merged 1 commit into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,12 @@ When using an external debugger, you can flash using this command:
```shell
west flash --erase
```

### LED pattern

| LED effect | Color | Meaning | Duration (seconds) |
|----------------|------------|----------------------------------------------|-----------------------------------------------------|
| Blinking | Yellow | Device is (re-)connecting to the LTE network | NA |
| Blinking slow | Blue | Device is actively polling cloud | 10 minutes after last config update or button press |
| Solid | Configured | Device has received a LED configuration | NA |
| Blinking rapid | Red | Fatal error, the device will reboot | NA |
8 changes: 8 additions & 0 deletions app/src/common/message_channel.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ ZBUS_CHAN_DEFINE(TRIGGER_CHAN,
ZBUS_MSG_INIT(0)
);

ZBUS_CHAN_DEFINE(TRIGGER_MODE_CHAN,
enum trigger_mode,
NULL,
NULL,
ZBUS_OBSERVERS_EMPTY,
ZBUS_MSG_INIT(0)
);

ZBUS_CHAN_DEFINE(FOTA_ONGOING_CHAN,
bool,
NULL,
Expand Down
8 changes: 7 additions & 1 deletion app/src/common/message_channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ enum trigger_type {
TRIGGER_DATA_SAMPLE,
};

enum trigger_mode {
TRIGGER_MODE_POLL = 0x1,
TRIGGER_MODE_NORMAL,
};

enum time_status {
TIME_AVAILABLE = 0x1,
};
Expand Down Expand Up @@ -88,7 +93,8 @@ ZBUS_CHAN_DECLARE(
NETWORK_CHAN,
PAYLOAD_CHAN,
TIME_CHAN,
TRIGGER_CHAN
TRIGGER_CHAN,
TRIGGER_MODE_CHAN
);

#ifdef __cplusplus
Expand Down
2 changes: 2 additions & 0 deletions app/src/modules/led/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

target_include_directories(app PRIVATE .)
target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/led.c)
target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/led_pwm.c)
220 changes: 162 additions & 58 deletions app/src/modules/led/led.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/zbus/zbus.h>
#include <zephyr/drivers/led.h>
#include <zephyr/drivers/pwm.h>
#include <dk_buttons_and_leds.h>

#include "message_channel.h"
#include "led_pwm.h"
#include "led.h"

/* Register log module */
LOG_MODULE_REGISTER(led, CONFIG_APP_LED_LOG_LEVEL);
Expand All @@ -26,33 +26,162 @@ ZBUS_LISTENER_DEFINE(led, led_callback);
/* Observe channels */
ZBUS_CHAN_ADD_OBS(ERROR_CHAN, led, 0);
ZBUS_CHAN_ADD_OBS(CONFIG_CHAN, led, 0);
ZBUS_CHAN_ADD_OBS(NETWORK_CHAN, led, 0);
ZBUS_CHAN_ADD_OBS(TRIGGER_MODE_CHAN, led, 0);

#define PWM_LED0_NODE DT_ALIAS(pwm_led0)
#define PWM_LED1_NODE DT_ALIAS(pwm_led1)
#define PWM_LED2_NODE DT_ALIAS(pwm_led2)

#if DT_NODE_HAS_STATUS(PWM_LED0_NODE, okay)
static const struct pwm_dt_spec led0 = PWM_DT_SPEC_GET(PWM_LED0_NODE);
#else
#error "Unsupported board: pwm-led 0 devicetree alias is not defined"
#endif
#if DT_NODE_HAS_STATUS(PWM_LED1_NODE, okay)
static const struct pwm_dt_spec led1 = PWM_DT_SPEC_GET(PWM_LED1_NODE);
#else
#error "Unsupported board: pwm-led 1 devicetree alias is not defined"
#endif
#if DT_NODE_HAS_STATUS(PWM_LED2_NODE, okay)
static const struct pwm_dt_spec led2 = PWM_DT_SPEC_GET(PWM_LED2_NODE);
#else
#error "Unsupported board: pwm-led 2 devicetree alias is not defined"
#endif

#define PWM_PERIOD 20000U
#define LIGHTNESS_MAX UINT8_MAX
/* Forward declarations */
static void led_pattern_update_work_fn(struct k_work *work);

/* Definition used to specify LED patterns that should hold forever. */
#define HOLD_FOREVER -1

/* List of LED patterns supported in the UI module. */
static struct led_pattern {
/* Variable used to construct a linked list of led patterns. */
sys_snode_t header;
/* LED state. */
enum led_state led_state;
/* Duration of the LED state. */
int16_t duration_sec;

/* RGB values for the LED state. */
uint8_t red;
uint8_t green;
uint8_t blue;

} led_pattern_list[LED_PATTERN_COUNT];

/* Linked list used to schedule multiple LED pattern transitions. */
static sys_slist_t pattern_transition_list = SYS_SLIST_STATIC_INIT(&pattern_transition_list);

/* Delayed work that is used to display and transition to the correct LED pattern depending on the
* internal state of the module.
*/
static K_WORK_DELAYABLE_DEFINE(led_pattern_update_work, led_pattern_update_work_fn);

/* Function that clears LED pattern transition list. */
static void transition_list_clear(void)
{
struct led_pattern *transition = NULL;
struct led_pattern *next_transition = NULL;

SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&pattern_transition_list,
transition,
next_transition,
header) {
sys_slist_remove(&pattern_transition_list, NULL, &transition->header);
};
}

/* Function that appends a LED state and a corresponding duration to the
* LED pattern transition list. If the LED state is set to LED_CONFIGURED, the RGB values are
* also appended to the list and used to set the desired color.
*/
static void transition_list_append(enum led_state led_state, int16_t duration_sec,
uint8_t red, uint8_t green, uint8_t blue)
{
led_pattern_list[led_state].led_state = led_state;
led_pattern_list[led_state].duration_sec = duration_sec;

if (led_state == LED_CONFIGURED) {
led_pattern_list[led_state].red = red;
led_pattern_list[led_state].green = green;
led_pattern_list[led_state].blue = blue;
}

sys_slist_append(&pattern_transition_list, &led_pattern_list[led_state].header);
}

static void led_pattern_update_work_fn(struct k_work *work)
{
ARG_UNUSED(work);

struct led_pattern *next_pattern;
static enum led_state previous_led_state = LED_PATTERN_COUNT;
sys_snode_t *node = sys_slist_get(&pattern_transition_list);

if (node == NULL) {
LOG_ERR("Cannot find any more LED pattern transitions");
return;
}

next_pattern = CONTAINER_OF(node, struct led_pattern, header);

/* Prevent the same LED led_state from being scheduled twice in a row. */
if (next_pattern->led_state != previous_led_state) {

if (next_pattern->led_state == LED_CONFIGURED) {
led_pwm_set_rgb(next_pattern->red, next_pattern->green, next_pattern->blue);
} else {
led_pwm_set_effect(next_pattern->led_state);
}

previous_led_state = next_pattern->led_state;
}

/* Even if the LED state is not updated due a match with the previous state a LED pattern
* update is scheduled. This will prolong the pattern until the LED pattern transition
* list is cleared.
*/
if (next_pattern->duration_sec > 0) {
k_work_reschedule(&led_pattern_update_work, K_SECONDS(next_pattern->duration_sec));
}
}

void led_callback(const struct zbus_channel *chan)
{
int err;
if (&TRIGGER_MODE_CHAN == chan) {
const enum trigger_mode *mode = zbus_chan_const_msg(chan);

if (*mode == TRIGGER_MODE_NORMAL) {
LOG_DBG("Trigger mode normal");

/* Clear all LED patterns and stop the PWM.
* I think we need to use the LED pwm API directly in some way here
* to ensure that we are disabling the PWM.
*/
transition_list_clear();
transition_list_append(LED_OFF, HOLD_FOREVER, 0, 0, 0);

k_work_reschedule(&led_pattern_update_work, K_NO_WAIT);

} else if (*mode == TRIGGER_MODE_POLL) {
LOG_DBG("Trigger mode poll");

transition_list_clear();

/* If we enter poll mode and the LED is configured, we set the configured
* color.
*/
if (led_pattern_list[LED_CONFIGURED].red != 0 ||
led_pattern_list[LED_CONFIGURED].green != 0 ||
led_pattern_list[LED_CONFIGURED].blue != 0) {

transition_list_append(LED_CONFIGURED, HOLD_FOREVER,
led_pattern_list[LED_CONFIGURED].red,
led_pattern_list[LED_CONFIGURED].green,
led_pattern_list[LED_CONFIGURED].blue);
} else {
transition_list_append(LED_POLL_MODE, HOLD_FOREVER, 0, 0, 0);
}

k_work_reschedule(&led_pattern_update_work, K_NO_WAIT);
}
}

if (&NETWORK_CHAN == chan) {

const enum network_status *status = zbus_chan_const_msg(chan);

if (*status == NETWORK_DISCONNECTED) {
LOG_DBG("Network disconnected");

transition_list_clear();
transition_list_append(LED_LTE_CONNECTING, HOLD_FOREVER, 0, 0, 0);

k_work_reschedule(&led_pattern_update_work, K_NO_WAIT);
}
}

if (&CONFIG_CHAN == chan) {
/* Get LED configuration from channel. */
Expand All @@ -67,46 +196,21 @@ void led_callback(const struct zbus_channel *chan)
LOG_DBG("LED configuration: red:%d, green:%d, blue:%d",
config->led_red, config->led_green, config->led_blue);

/* Red LED */
err = pwm_set_dt(&led0, PWM_USEC(PWM_PERIOD),
PWM_USEC((PWM_PERIOD * config->led_red) / LIGHTNESS_MAX));
if (err) {
LOG_ERR("pwm_set_dt, error:%d", err);
SEND_FATAL_ERROR();
return;
}

/* Blue LED */
err = pwm_set_dt(&led1, PWM_USEC(PWM_PERIOD),
PWM_USEC((PWM_PERIOD * config->led_blue) / LIGHTNESS_MAX));
if (err) {
LOG_ERR("pwm_set_dt, error:%d", err);
SEND_FATAL_ERROR();
return;
}
transition_list_clear();
transition_list_append(LED_CONFIGURED, HOLD_FOREVER,
(uint8_t)config->led_red,
(uint8_t)config->led_green,
(uint8_t)config->led_blue);

/* Green LED */
err = pwm_set_dt(&led2, PWM_USEC(PWM_PERIOD),
PWM_USEC((PWM_PERIOD * config->led_green) / LIGHTNESS_MAX));
if (err) {
LOG_ERR("pwm_set_dt, error:%d", err);
SEND_FATAL_ERROR();
return;
}
k_work_reschedule(&led_pattern_update_work, K_NO_WAIT);
}

if (&ERROR_CHAN == chan) {
/* Red LED */

const enum error_type *type = zbus_chan_const_msg(chan);

if (*type == ERROR_FATAL) {
err = dk_set_led_on(DK_LED1);
if (err) {
LOG_ERR("dk_set_led_on, error:%d", err);
SEND_FATAL_ERROR();
return;
}
transition_list_clear();
transition_list_append(LED_ERROR_SYSTEM_FAULT, HOLD_FOREVER, 0, 0, 0);
}
}
}
Expand Down
75 changes: 75 additions & 0 deletions app/src/modules/led/led.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (c) 2024 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/

/**@file
*
* @brief LED module.
*
* Module that handles LED behaviour.
*/

#ifndef LED_H__
#define LED_H__

#include <zephyr/kernel.h>

#include "led_effect.h"

#ifdef __cplusplus
extern "C" {
#endif

#define LED_1 1
#define LED_2 2
#define LED_3 3
#define LED_4 4

#define LED_ON(x) (x)
#define LED_BLINK(x) ((x) << 8)
#define LED_GET_ON(x) ((x)&0xFF)
#define LED_GET_BLINK(x) (((x) >> 8) & 0xFF)

#define LED_ON_PERIOD_NORMAL 500
#define LED_OFF_PERIOD_NORMAL 2000
#define LED_ON_PERIOD_ERROR 500
#define LED_OFF_PERIOD_ERROR 500
#define LED_ON_PERIOD_SHORT 350
#define LED_OFF_PERIOD_SHORT 350
#define LED_ON_PERIOD_STROBE 50
#define LED_OFF_PERIOD_STROBE 50
#define LED_OFF_PERIOD_LONG 4000

#define LED_MAX 50

#define LED_COLOR_OFF LED_COLOR(0, 0, 0)
#define LED_COLOR_RED LED_COLOR(LED_MAX, 0, 0)
#define LED_COLOR_GREEN LED_COLOR(0, LED_MAX, 0)
#define LED_COLOR_BLUE LED_COLOR(0, 0, LED_MAX)
#define LED_COLOR_YELLOW LED_COLOR(LED_MAX, LED_MAX, 0)
#define LED_COLOR_CYAN LED_COLOR(0, LED_MAX, LED_MAX)
#define LED_COLOR_PURPLE LED_COLOR(LED_MAX, 0, LED_MAX)
#define LED_COLOR_WHITE LED_COLOR(LED_MAX, LED_MAX, LED_MAX)

#define LED_LTE_CONNECTING_COLOR LED_COLOR_YELLOW
#define LED_POLL_MODE_COLOR LED_COLOR_BLUE
#define LED_ERROR_SYSTEM_FAULT_COLOR LED_COLOR_RED
#define LED_OFF_COLOR LED_COLOR_OFF

/**@brief LED state pattern definitions. */
enum led_state {
LED_LTE_CONNECTING,
LED_POLL_MODE,
LED_ERROR_SYSTEM_FAULT,
LED_CONFIGURED,
LED_OFF,
LED_PATTERN_COUNT,
};

#ifdef __cplusplus
}
#endif

#endif /* LED_H__ */
8 changes: 8 additions & 0 deletions app/src/modules/led/led/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#
# Copyright (c) 2024 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

target_include_directories(app PRIVATE .)
target_sources_ifdef(CONFIG_LED_USE_PWM app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/led_pwm.c)
Loading
Loading