-
Notifications
You must be signed in to change notification settings - Fork 6.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
samples: SMF: LVGL: SMF-based Calculator
This sample crates a touchscreen desk calculator based on the sample state machine in 'Practical UML Statecharts in C/C++' by Miro Samek. Sample should build and run on any touchscreen-enabled board with sufficient resources. Tested on disco_l475_iot1 board with adafruit_2_8_tft_touch_v2 touchscreen. Signed-off-by: Glenn Andrews <[email protected]>
- Loading branch information
1 parent
a912bb6
commit b8f5869
Showing
10 changed files
with
1,118 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
set(CMAKE_CXX_FLAGS "-fstack-usage") | ||
cmake_minimum_required(VERSION 3.20.0) | ||
|
||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) | ||
project(smf_psicc2_demo) | ||
|
||
target_sources(app PRIVATE src/main.c) | ||
target_sources(app PRIVATE src/smf_console_cmds.c) | ||
target_sources(app PRIVATE src/smf_calculator_thread.c) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
.. zephyr:code-sample:: smf_calculator | ||
:name: SMF Calculator | ||
:relevant-api: smf | ||
|
||
Create a simple desk calculator using the State Machine Framework. | ||
|
||
Overview | ||
******** | ||
|
||
This sample creates a basic desk calculator driven by a state machine written | ||
with the :ref:`State Machine Framework <smf>`. | ||
|
||
The 'business logic' of the calculator is based on the statechart given in | ||
Fig 2.18 of *Practical UML Statecharts in C/C++* 2nd Edition by Miro Samek. | ||
This uses a three-layer hierarchical statechart to handle situations such as | ||
ignoring leading zeroes, and disallowing multiple decimal points. | ||
|
||
The statechart has been slightly modified to display different output on the | ||
screen in the ``op_entered`` state depending on if a previous result is | ||
available or not. | ||
|
||
.. figure:: img/smf_calculator.png | ||
:align: center | ||
:alt: SMF Calculator Statechart | ||
:figclass: align-center | ||
|
||
Statechart for the SMF Calculator business logic. | ||
|
||
The graphical interface uses an LVGL buton matrix for input and text label for | ||
output, based on the sample in samples/drivers/display. The state machine updates | ||
the output text label after every call to :c:func:`smf_run_state`. | ||
|
||
:kconfig:option:`CONFIG_LV_Z_VDB_SIZE` has been reduced to 14% to allow it to run | ||
on RAM-constrained boards like the :ref:`disco_l475_iot1_board`. | ||
|
||
Requirements | ||
************ | ||
|
||
The GUI should work with any touchscreen display supported by Zephyr. The shield | ||
must be passed to ``west build`` using the ``--shield`` option, e.g. | ||
``--shield adafruit_2_8_tft_touch_v2`` | ||
|
||
List of Arduino-based touchscreen shields: | ||
|
||
- :ref:`adafruit_2_8_tft_touch_v2` | ||
- :ref:`buydisplay_2_8_tft_touch_arduino` | ||
- :ref:`buydisplay_3_5_tft_touch_arduino` | ||
|
||
The demo should also work on STM32 Discovery Kits with built-in touchscreens e.g. | ||
|
||
- :ref:`stm32f412g_disco_board` | ||
- :ref:`st25dv_mb1283_disco_board` | ||
- :ref:`stm32f7508_dk_board` | ||
- :ref:`stm32f769i_disco_board` | ||
|
||
etc. These will not need a shield defined as the touchscreen is built-in. | ||
|
||
|
||
Building and Running | ||
******************** | ||
|
||
Below is an example on how to build for a :ref:`disco_l475_iot1_board` board with | ||
a :ref:`adafruit_2_8_tft_touch_v2`. | ||
|
||
.. zephyr-app-commands:: | ||
:zephyr-app: samples/subsys/smf/smf_calculator | ||
:board: disco_l475_iot1 | ||
:goals: build | ||
:shield: adafruit_2_8_tft_touch_v2 | ||
:compact: | ||
|
||
For testing purpose without the need of any hardware, the :ref:`native_sim <native_sim>` | ||
board is also supported and can be built as follows; | ||
|
||
.. zephyr-app-commands:: | ||
:zephyr-app: samples/subsys/smf/smf_calculator | ||
:board: native_sim | ||
:goals: build | ||
:compact: | ||
|
||
CLI control | ||
=========== | ||
|
||
As well as control through the GUI, the calculator can be controlled through the CLI. | ||
The ``key <key>`` command sends a keypress to the state machine. Valid keys are | ||
``0`` through ``9`` for numbers, ``.``, ``+``, ``-``, ``*``, ``/`` and ``=`` to | ||
perform the expected function, ``C`` for Cancel, and ``E`` for Cancel Entry. | ||
|
||
GUI update speed on the :ref:`disco_l475_iot1_board` is of the order of 0.8s due to the | ||
small video buffer. | ||
|
||
References | ||
********** | ||
|
||
*Practical UML Statecharts in C/C++* 2nd Edition by Miro Samek | ||
https://www.state-machine.com/psicc2 |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
CONFIG_DEBUG=y | ||
CONFIG_LOG=y | ||
CONFIG_SHELL=y | ||
|
||
# Enable the state machine framework | ||
CONFIG_SMF=y | ||
CONFIG_SMF_ANCESTOR_SUPPORT=y | ||
CONFIG_SMF_INITIAL_TRANSITION=y | ||
|
||
# Enable floating point support | ||
CONFIG_PICOLIBC_IO_FLOAT=y | ||
CONFIG_CBPRINTF_FP_SUPPORT=y | ||
|
||
# Enable thread awareness for debugging tools supporting it | ||
CONFIG_DEBUG_THREAD_INFO=y | ||
|
||
# enable to use thread names | ||
CONFIG_THREAD_NAME=y | ||
|
||
# Display Options | ||
CONFIG_DISPLAY=y | ||
CONFIG_DISPLAY_LOG_LEVEL_ERR=y | ||
|
||
# LVGL Options | ||
CONFIG_LV_Z_MEM_POOL_SIZE=8192 | ||
CONFIG_LV_Z_SHELL=y | ||
CONFIG_MAIN_STACK_SIZE=2048 | ||
# Percentage of screen size for a video buffer | ||
# 14% ~= 32k on adafruit_2_8_tft_touch_v2 | ||
CONFIG_LV_Z_VDB_SIZE=14 | ||
|
||
CONFIG_LVGL=y | ||
CONFIG_LV_MEM_CUSTOM=y | ||
CONFIG_LV_USE_LOG=y | ||
CONFIG_LV_USE_LABEL=y | ||
CONFIG_LV_USE_BTN=y | ||
CONFIG_LV_USE_ARC=y | ||
CONFIG_LV_USE_IMG=y | ||
CONFIG_LV_USE_MONKEY=y | ||
CONFIG_LV_FONT_MONTSERRAT_14=y |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
sample: | ||
description: SMF Calculator GUI Application | ||
name: lvgl | ||
tests: | ||
sample.display.smf.calculator: | ||
filter: dt_chosen_enabled("zephyr,display") | ||
# Sample takes ~300k on disco_l475_iot1 board, add 50k just in case. | ||
# Ram usage is 60k on disco_l475_iot1 board with adafruit_2_8_tft_touch_v2. | ||
# Can be reduced by changing CONFIG_LV_Z_VDB_SIZE. | ||
min_flash: 350 | ||
min_ram: 64 | ||
harness: none | ||
tags: | ||
- samples | ||
- display | ||
- gui | ||
- lvgl | ||
- smf | ||
modules: | ||
- lvgl | ||
integration_platforms: | ||
- native_sim/native/64 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
/* | ||
* Copyright (c) 2024 Glenn Andrews | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
#include "main.h" | ||
#include <zephyr/kernel.h> | ||
#include "smf_calculator_thread.h" | ||
#include <zephyr/drivers/display.h> | ||
#include <lvgl.h> | ||
#include <stdio.h> | ||
#include <string.h> | ||
#include <lvgl_input_device.h> | ||
#include <zephyr/logging/log.h> | ||
|
||
#define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL | ||
LOG_MODULE_REGISTER(main_app); | ||
|
||
K_MSGQ_DEFINE(output_msgq, CALCULATOR_STRING_LENGTH, 2, 1); | ||
|
||
#define CALCULATOR_BUTTON_LABEL_LENGTH 4 | ||
struct calculator_button { | ||
char label[CALCULATOR_BUTTON_LABEL_LENGTH]; | ||
struct calculator_event event; | ||
}; | ||
|
||
/* clang-format off */ | ||
struct calculator_button buttons[] = { | ||
{.label = "CE", .event = {.event_id = CANCEL_ENTRY, .operand = 'E'}}, | ||
{.label = "C", .event = {.event_id = CANCEL_BUTTON, .operand = 'C'}}, | ||
{.label = "7", .event = {.event_id = DIGIT_1_9, .operand = '7'}}, | ||
{.label = "8", .event = {.event_id = DIGIT_1_9, .operand = '8'}}, | ||
{.label = "9", .event = {.event_id = DIGIT_1_9, .operand = '9'}}, | ||
{.label = "/", .event = {.event_id = OPERATOR, .operand = '/'}}, | ||
{.label = "4", .event = {.event_id = DIGIT_1_9, .operand = '4'}}, | ||
{.label = "5", .event = {.event_id = DIGIT_1_9, .operand = '5'}}, | ||
{.label = "6", .event = {.event_id = DIGIT_1_9, .operand = '6'}}, | ||
{.label = "*", .event = {.event_id = OPERATOR, .operand = '*'}}, | ||
{.label = "1", .event = {.event_id = DIGIT_1_9, .operand = '1'}}, | ||
{.label = "2", .event = {.event_id = DIGIT_1_9, .operand = '2'}}, | ||
{.label = "3", .event = {.event_id = DIGIT_1_9, .operand = '3'}}, | ||
{.label = "-", .event = {.event_id = OPERATOR, .operand = '-'}}, | ||
{.label = "0", .event = {.event_id = DIGIT_0, .operand = '0'}}, | ||
{.label = ".", .event = {.event_id = DECIMAL_POINT, .operand = '.'}}, | ||
{.label = "=", .event = {.event_id = EQUALS, .operand = '='}}, | ||
{.label = "+", .event = {.event_id = OPERATOR, .operand = '+'}}, | ||
}; | ||
/* clang-format on */ | ||
|
||
/* Where the result is printed */ | ||
static lv_obj_t *result_label; | ||
|
||
void update_display(const char *output) | ||
{ | ||
while (k_msgq_put(&output_msgq, output, K_NO_WAIT) != 0) { | ||
k_msgq_purge(&output_msgq); | ||
} | ||
} | ||
|
||
static void lv_btn_matrix_click_callback(lv_event_t *e) | ||
{ | ||
lv_event_code_t code = lv_event_get_code(e); | ||
lv_obj_t *obj = lv_event_get_target(e); | ||
|
||
if (code == LV_EVENT_PRESSED) { | ||
uint32_t id = lv_btnmatrix_get_selected_btn(obj); | ||
|
||
if (id > ARRAY_SIZE(buttons)) { | ||
LOG_ERR("Invalid button: %d", id); | ||
return; | ||
} | ||
|
||
int rc = post_calculator_event(&buttons[id].event, K_FOREVER); | ||
|
||
if (rc != 0) { | ||
LOG_ERR("could not post to msgq: %d", rc); | ||
} | ||
} | ||
} | ||
|
||
int setup_display(void) | ||
{ | ||
const struct device *display_dev; | ||
|
||
display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display)); | ||
if (!device_is_ready(display_dev)) { | ||
LOG_ERR("Device not ready, aborting setup"); | ||
return -ENODEV; | ||
} | ||
|
||
/* clang-format off */ | ||
static const char *const btnm_map[] = { | ||
buttons[0].label, buttons[1].label, "\n", | ||
buttons[2].label, buttons[3].label, buttons[4].label, buttons[5].label, "\n", | ||
buttons[6].label, buttons[7].label, buttons[8].label, buttons[9].label, "\n", | ||
buttons[10].label, buttons[11].label, buttons[12].label, buttons[13].label, "\n", | ||
buttons[14].label, buttons[15].label, buttons[16].label, buttons[17].label, "\n" | ||
}; | ||
/* clang-format on */ | ||
|
||
lv_obj_t *btn_matrix = lv_btnmatrix_create(lv_scr_act()); | ||
|
||
lv_obj_align(btn_matrix, LV_ALIGN_BOTTOM_MID, 0, 0); | ||
lv_btnmatrix_set_map(btn_matrix, (const char **)btnm_map); | ||
lv_obj_set_size(btn_matrix, lv_pct(CALC_BTN_WIDTH_PCT), lv_pct(CALC_BTN_HEIGHT_PCT)); | ||
lv_obj_add_event_cb(btn_matrix, lv_btn_matrix_click_callback, LV_EVENT_ALL, NULL); | ||
|
||
result_label = lv_label_create(lv_scr_act()); | ||
lv_obj_set_width(result_label, lv_pct(CALC_RESULT_WIDTH_PCT)); | ||
lv_obj_set_style_text_align(result_label, LV_TEXT_ALIGN_RIGHT, 0); | ||
lv_obj_align(result_label, LV_ALIGN_TOP_MID, 0, lv_pct(CALC_RESULT_OFFSET_PCT)); | ||
|
||
static lv_style_t style_shadow; | ||
|
||
lv_style_init(&style_shadow); | ||
lv_style_set_shadow_width(&style_shadow, 5); | ||
lv_style_set_shadow_spread(&style_shadow, 2); | ||
lv_style_set_shadow_color(&style_shadow, lv_palette_main(LV_PALETTE_GREY)); | ||
lv_obj_add_style(result_label, &style_shadow, 0); | ||
update_display("0"); | ||
|
||
lv_task_handler(); | ||
display_blanking_off(display_dev); | ||
|
||
return 0; | ||
} | ||
|
||
int main(void) | ||
{ | ||
printk("SMF Desk Caclualtor Demo\n"); | ||
|
||
int rc = setup_display(); | ||
|
||
if (rc != 0) { | ||
return rc; | ||
} | ||
|
||
while (1) { | ||
char output[CALCULATOR_STRING_LENGTH]; | ||
|
||
if (k_msgq_get(&output_msgq, output, K_MSEC(50)) == 0) { | ||
lv_label_set_text(result_label, output); | ||
} | ||
|
||
lv_task_handler(); | ||
} | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
/* | ||
* Copyright (c) 2024 Glenn Andrews | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
/* Constants for control sizes as a percentage of screen size. */ | ||
#define CALC_BTN_WIDTH_PCT 90 | ||
#define CALC_BTN_HEIGHT_PCT 80 | ||
#define CALC_RESULT_WIDTH_PCT 70 | ||
#define CALC_RESULT_OFFSET_PCT 10 | ||
|
||
void update_display(const char *output); |
Oops, something went wrong.