diff --git a/.gitmodules b/.gitmodules index 3f2b069..a972e88 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "note-c"] path = note-c - url = ../note-c.git + url = git@github.com:blues/note-c.git + branch = master \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 251e7c5..fcf3154 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -14,7 +14,7 @@ "isDefault": true }, "command": [ - "west build --board ${config:board} --pristine=always ${workspaceFolder}" + "west build --board ${config:board} --pristine=always ${workspaceFolder}/example" ] }, { @@ -27,7 +27,7 @@ "kind": "build" }, "command": [ - "west build --board ${config:board} ${workspaceFolder}" + "west build --board ${config:board} ${workspaceFolder}/example" ] }, { @@ -90,4 +90,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 81b900f..913c747 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,80 +1,24 @@ -# Copyright 2022 Blues Inc. All rights reserved. -# Use of this source code is governed by licenses granted by the -# copyright holder including that found in the LICENSE file. - -# SPDX-License-Identifier: MIT - -# Set CMake policy behavior -cmake_minimum_required(VERSION 3.20.0) - -# Load Zephyr Package -find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) - -# Basic Project Configuration -project(note-zephyr - VERSION 1.0.0 - LANGUAGES C ASM -) -set(CMAKE_C_STANDARD 11) -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_C_EXTENSIONS ON) # required for inline asm - -# Create variables to alias path names -set(NOTE_C ${CMAKE_CURRENT_LIST_DIR}/note-c) -set(SRC ${CMAKE_CURRENT_LIST_DIR}/src) - -# Set global compile settings -zephyr_get_compile_options_for_lang_as_string(C zephyr_options) -message(STATUS ${zephyr_options}) -message(STATUS) # Insert blank line for readability -zephyr_compile_options( - -ggdb - -Og - -Wall - # -Wextra - -Wno-unused-parameter - # -Wpedantic -) -zephyr_get_compile_options_for_lang_as_string(C zephyr_options) -message(STATUS ${zephyr_options}) -message(STATUS) # Insert blank line for readability - -# Zephyr Example Application -target_sources(app - PRIVATE ${SRC}/main.c - PRIVATE ${SRC}/note_c_hooks.c -) - -# Build additional 3rd party libs (e.g. `note-c`) with `app` -target_sources(app - PRIVATE ${NOTE_C}/n_atof.c - PRIVATE ${NOTE_C}/n_b64.c - PRIVATE ${NOTE_C}/n_cjson.c - PRIVATE ${NOTE_C}/n_cjson_helpers.c - PRIVATE ${NOTE_C}/n_const.c - PRIVATE ${NOTE_C}/n_ftoa.c - PRIVATE ${NOTE_C}/n_helpers.c - PRIVATE ${NOTE_C}/n_hooks.c - PRIVATE ${NOTE_C}/n_i2c.c - PRIVATE ${NOTE_C}/n_md5.c - PRIVATE ${NOTE_C}/n_printf.c - PRIVATE ${NOTE_C}/n_request.c - PRIVATE ${NOTE_C}/n_serial.c - PRIVATE ${NOTE_C}/n_str.c - PRIVATE ${NOTE_C}/n_ua.c -) - -target_include_directories(app - PRIVATE ${NOTE_C} -) - -# WARNING: These options are overriden by `zephyr_compile_options()` -target_compile_options(app - PRIVATE -ggdb - PRIVATE -Og - PRIVATE -Wall - PRIVATE -Wextra - # PRIVATE -Wpedantic - PRIVATE -Wimplicit-fallthrough=2 - PRIVATE -Wunused-parameter -) +if(CONFIG_NOTECARD) + set(NOTE_C_DIR ${ZEPHYR_CURRENT_MODULE_DIR}/note-c) + zephyr_include_directories(. ${NOTE_C_DIR}) + zephyr_library_sources( + note_c_hooks.c + ${NOTE_C_DIR}/n_atof.c + ${NOTE_C_DIR}/n_atof.c + ${NOTE_C_DIR}/n_b64.c + ${NOTE_C_DIR}/n_cjson.c + ${NOTE_C_DIR}/n_cjson_helpers.c + ${NOTE_C_DIR}/n_cobs.c + ${NOTE_C_DIR}/n_const.c + ${NOTE_C_DIR}/n_ftoa.c + ${NOTE_C_DIR}/n_helpers.c + ${NOTE_C_DIR}/n_hooks.c + ${NOTE_C_DIR}/n_i2c.c + ${NOTE_C_DIR}/n_md5.c + ${NOTE_C_DIR}/n_printf.c + ${NOTE_C_DIR}/n_request.c + ${NOTE_C_DIR}/n_serial.c + ${NOTE_C_DIR}/n_str.c + ${NOTE_C_DIR}/n_ua.c + ) +endif() diff --git a/KConfig b/KConfig new file mode 100644 index 0000000..f86fb3b --- /dev/null +++ b/KConfig @@ -0,0 +1,6 @@ +config NOTECARD + bool "Enable the helper library for the Blues Wireless Notecard" + default n + depends on I2C + help + Module for controlling blues.io notecard using the note-c library. Requires an device tree alias for the notecard. \ No newline at end of file diff --git a/README.md b/README.md index 21f1097..3d7e5c2 100644 --- a/README.md +++ b/README.md @@ -258,3 +258,44 @@ as `/dev/ttyACM0`. - Press green triangle at the top of the **Run and Debug** panel. - Select **Run > Start Debugging** from the application menu. - Press the function key, `F5`. + +Using the West Module +--------------------- + +To get started with this module, you will need to add it to your Zephyr project's `west.yml` manifest file and then add the required overlay to your Zephyr project's device tree. + +### Manifest + +To use this module, add the following to your Zephyr project's `west.yml` manifest file, for example: + +```yaml + projects: + # Your other modules here + - name: notecard + path: modules/notecard + revision: main + submodules: true + url: https://github.com/blues/note-zephyr +``` + +Then, run `west update` to pull the module into your project. + +### Overlays + +The helper functions (`note_c_hooks.c`) provide controls to communicate with a Notecard attached to the Zephyr host via i2c. + +As such, the helper functions expect the target device to use a DT alias of `notecard` for the i2c bus. +You should ensure the host device you are targetting has this alias defined in its DT overlay. + +```dts +/ { + aliases { + // for example, using the i2c0 bus + notecard = &i2c0; + }; +}; +``` + +### Example + +Included in this repo is a simple example for using the west module to send a note using the Notecard. See the [example](example/README.md) for more details. diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..f5dbb44 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.13.1) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(app LANGUAGES C) + +target_sources(app PRIVATE src/main.c) diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..0430db7 --- /dev/null +++ b/example/README.md @@ -0,0 +1,19 @@ +# Example + +This example uses the `note-c` library to send a note using the Notecard, to Notehub. Initially the example will configure the notecard and then will send a note every 10 seconds with the state of the onboard LED. This example specifically targets the [Swan MCU](https://blues.com/products/swan/) and one of the Blues [carrier boards](https://blues.com/products/notecarrier/) (when connected via i2c) and provides building / flashing instructions for this board, using the STLINK-V3 debugger. + +## Build + +To build this example, you can use the following command from the root of this repo: + +```bash +west build -b swan_r5 example +``` + +## Flash + +To flash this example to the Swan MCU, you can use the following command: + +```bash +west flash +``` diff --git a/example/boards/swan_r5.overlay b/example/boards/swan_r5.overlay new file mode 100644 index 0000000..3d018cb --- /dev/null +++ b/example/boards/swan_r5.overlay @@ -0,0 +1,6 @@ +/ { + aliases { + // for example, using the i2c1 bus on the Blues Swan Feather Board + notecard = &i2c1; + }; +}; \ No newline at end of file diff --git a/prj.conf b/example/prj.conf similarity index 83% rename from prj.conf rename to example/prj.conf index 384d806..d69633a 100644 --- a/prj.conf +++ b/example/prj.conf @@ -9,3 +9,6 @@ CONFIG_NEWLIB_LIBC=y # Debugging Configuration CONFIG_PRINTK=y + +# Notecard Library +CONFIG_NOTECARD=y \ No newline at end of file diff --git a/example/src/main.c b/example/src/main.c new file mode 100644 index 0000000..b5f5174 --- /dev/null +++ b/example/src/main.c @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2023 Blues Inc. + * + * MIT License. Use of this source code is governed by licenses granted + * by the copyright holder including that found in the LICENSE file. + * + * Author: Zachary J. Fields + */ + +// Include Zephyr Headers +#include +#include + +// Include Notecard note-c library +#include + +// Notecard node-c helper methods +#include "note_c_hooks.h" + +// Uncomment the define below and replace com.your-company:your-product-name +// with your ProductUID. +// #define PRODUCT_UID "com.your-company:your-product-name" + +#ifndef PRODUCT_UID +#define PRODUCT_UID "" +#pragma message \ + "PRODUCT_UID is not defined in this example. Please ensure your Notecard has a product identifier set before running this example or define it in code here. More details at https://bit.ly/product-uid" +#endif + +// 10000 msec = 10 sec +#define SLEEP_TIME_MS 10000 + +// The devicetree node identifier for the "led0" alias. +#define LED0_NODE DT_ALIAS(led0) + +#if DT_NODE_HAS_STATUS(LED0_NODE, okay) + +static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios); + +#else + +// A build error on this line means your board is unsupported. +// See the sample documentation for information on how to fix this. +#error "Unsupported board: led0 devicetree alias is not defined" + +#endif + +int main(void) +{ + int ret; + + printk("[INFO] main(): Initializing...\n"); + + // Initialize note-c hooks + NoteSetUserAgent((char *)"note-zephyr"); + NoteSetFnDefault(malloc, free, platform_delay, platform_millis); + NoteSetFnDebugOutput(note_log_print); + NoteSetFnI2C(NOTE_I2C_ADDR_DEFAULT, NOTE_I2C_MAX_DEFAULT, note_i2c_reset, note_i2c_transmit, + note_i2c_receive); + + // Send a Notecard hub.set using note-c + J *req = NoteNewRequest("hub.set"); + if (req) { + JAddStringToObject(req, "product", PRODUCT_UID); + JAddStringToObject(req, "mode", "continuous"); + JAddStringToObject(req, "sn", "zephyr-blink"); + if (!NoteRequest(req)) { + printk("Failed to configure Notecard.\n"); + return -1; + } + } else { + printk("Failed to allocate memory.\n"); + return -1; + } + + if (!gpio_is_ready_dt(&led)) { + printk("Failed to activate LED.\n"); + return -1; + } + + ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); + if (ret < 0) { + printk("Failed to configure LED.\n"); + return ret; + } + + // Application Loop + printk("[INFO] main(): Entering loop...\n"); + while (1) { + // Toggle LED state + ret = gpio_pin_toggle_dt(&led); + if (ret < 0) { + printk("Failed to toggle LED.\n"); + break; + } + + // Report LED state to Notehub.io + J *req = NoteNewRequest("note.add"); + if (req) { + JAddBoolToObject(req, "sync", true); + J *body = JAddObjectToObject(req, "body"); + JAddBoolToObject(body, "LED", gpio_pin_get(led.port, led.pin)); + if (!NoteRequest(req)) { + printk("Failed to submit Note to Notecard.\n"); + ret = -1; + break; + } + } else { + printk("Failed to allocate memory.\n"); + ret = -1; + break; + } + + // Wait to iterate + k_msleep(SLEEP_TIME_MS); + } + + return ret; +} \ No newline at end of file diff --git a/note-c b/note-c index 2b31718..2d3f0b1 160000 --- a/note-c +++ b/note-c @@ -1 +1 @@ -Subproject commit 2b31718ec01a9000f8f62cea848449eacfe50807 +Subproject commit 2d3f0b19a975268a42e0b82d90e8c39d9c3a99b9 diff --git a/note_c_hooks.c b/note_c_hooks.c new file mode 100644 index 0000000..ae9e155 --- /dev/null +++ b/note_c_hooks.c @@ -0,0 +1,105 @@ +#include "note_c_hooks.h" + +#include +#include +#include + +static const size_t REQUEST_HEADER_SIZE = 2; + +const struct device *i2c_dev = NULL; +bool i2c_initialized = false; + +LOG_MODULE_REGISTER(note, LOG_LEVEL_DBG); + +uint32_t platform_millis(void) +{ + return (uint32_t)k_uptime_get(); +} + +void platform_delay(uint32_t ms) +{ + k_msleep(ms); +} + +const char *note_i2c_receive(uint16_t device_address_, uint8_t *buffer_, uint16_t size_, + uint32_t *available_) +{ + // Let the Notecard know that we are getting ready to read some data + uint8_t size_buf[2]; + size_buf[0] = 0; + size_buf[1] = (uint8_t)size_; + uint8_t write_result = i2c_write(i2c_dev, size_buf, sizeof(size_buf), device_address_); + + if (write_result != 0) { + return "i2c: Unable to initate read from the Notecard\n"; + } + + // Read from the Notecard and copy the response bytes into the response buffer + const int request_length = size_ + REQUEST_HEADER_SIZE; + uint8_t read_buf[256]; + uint8_t read_result = i2c_read(i2c_dev, read_buf, request_length, device_address_); + + if (read_result != 0) { + return "i2c: Unable to receive data from the Notecard.\n"; + } else { + *available_ = (uint32_t)read_buf[0]; + uint8_t bytes_to_read = read_buf[1]; + for (size_t i = 0; i < bytes_to_read; i++) { + buffer_[i] = read_buf[i + 2]; + } + + return NULL; + } +} + +bool note_i2c_reset(uint16_t device_address_) +{ + (void)device_address_; + + if (i2c_initialized) { + return true; + } + + if (!i2c_dev) { + i2c_dev = DEVICE_DT_GET(DT_ALIAS(notecard)); + } + + if (!device_is_ready(i2c_dev)) { + LOG_ERR("i2c: Device is not ready.\n"); + return false; + } + + LOG_DBG("i2c: Device is ready.\n"); + + i2c_initialized = true; + return true; +} + +const char *note_i2c_transmit(uint16_t device_address_, uint8_t *buffer_, uint16_t size_) +{ + // Create a buffer that contains the number of bytes and the data to write to the Notecard + uint8_t write_buf[size_ + 1]; + write_buf[0] = (uint8_t)size_; + for (size_t i = 0; i < size_; i++) { + write_buf[i + 1] = buffer_[i]; + } + + // Write the message + uint8_t write_result = i2c_write(i2c_dev, write_buf, sizeof(write_buf), device_address_); + + if (write_result != 0) { + return "i2c: Unable to transmit data to the Notecard\n"; + } else { + return NULL; + } +} + +size_t note_log_print(const char *message_) +{ + if (message_) { + LOG_INF("%s", message_); + return 1; + } + + return 0; +} diff --git a/src/note_c_hooks.h b/note_c_hooks.h similarity index 100% rename from src/note_c_hooks.h rename to note_c_hooks.h diff --git a/src/main.c b/src/main.c deleted file mode 100644 index 483af14..0000000 --- a/src/main.c +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2023 Blues Inc. - * - * MIT License. Use of this source code is governed by licenses granted - * by the copyright holder including that found in the LICENSE file. - * - * Author: Zachary J. Fields - */ - -// Include Zephyr Headers -#include -#include - -// Include Notecard note-c library -#include - -// Notecard node-c helper methods -#include "note_c_hooks.h" - -// Uncomment the define below and replace com.your-company:your-product-name -// with your ProductUID. -// #define PRODUCT_UID "com.your-company:your-product-name" - -#ifndef PRODUCT_UID -#define PRODUCT_UID "" -#pragma message "PRODUCT_UID is not defined in this example. Please ensure your Notecard has a product identifier set before running this example or define it in code here. More details at https://bit.ly/product-uid" -#endif - -// 10000 msec = 10 sec -#define SLEEP_TIME_MS 10000 - -// The devicetree node identifier for the "led0" alias. -#define LED0_NODE DT_ALIAS(led0) - -#if DT_NODE_HAS_STATUS(LED0_NODE, okay) - -static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios); - -#else - -// A build error on this line means your board is unsupported. -// See the sample documentation for information on how to fix this. -#error "Unsupported board: led0 devicetree alias is not defined" - -#endif - -int main(void) -{ - int ret; - - printk("[INFO] main(): Initializing...\n"); - - // Initialize note-c hooks - NoteSetUserAgent((char *)"note-zephyr"); - NoteSetFnDefault(malloc, free, platform_delay, platform_millis); - NoteSetFnDebugOutput(note_log_print); - NoteSetFnI2C(NOTE_I2C_ADDR_DEFAULT, NOTE_I2C_MAX_DEFAULT, note_i2c_reset, - note_i2c_transmit, note_i2c_receive); - - // Send a Notecard hub.set using note-c - J *req = NoteNewRequest("hub.set"); - if (req) - { - JAddStringToObject(req, "product", PRODUCT_UID); - JAddStringToObject(req, "mode", "continuous"); - JAddStringToObject(req, "sn", "zephyr-blink"); - if (!NoteRequest(req)) - { - printk("Failed to configure Notecard.\n"); - return -1; - } - } - else - { - printk("Failed to allocate memory.\n"); - return -1; - } - - if (!gpio_is_ready_dt(&led)) - { - printk("Failed to activate LED.\n"); - return -1; - } - - ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE); - if (ret < 0) - { - printk("Failed to configure LED.\n"); - return ret; - } - - // Application Loop - printk("[INFO] main(): Entering loop...\n"); - while (1) - { - // Toggle LED state - ret = gpio_pin_toggle_dt(&led); - if (ret < 0) - { - printk("Failed to toggle LED.\n"); - break; - } - - // Report LED state to Notehub.io - J *req = NoteNewRequest("note.add"); - if (req) - { - JAddBoolToObject(req, "sync", true); - J *body = JAddObjectToObject(req, "body"); - JAddBoolToObject(body, "LED", gpio_pin_get(led.port, led.pin)); - if (!NoteRequest(req)) - { - printk("Failed to submit Note to Notecard.\n"); - ret = -1; - break; - } - } - else - { - printk("Failed to allocate memory.\n"); - ret = -1; - break; - } - - // Wait to iterate - k_msleep(SLEEP_TIME_MS); - } - - return ret; -} diff --git a/src/note_c_hooks.c b/src/note_c_hooks.c deleted file mode 100644 index 3c84725..0000000 --- a/src/note_c_hooks.c +++ /dev/null @@ -1,95 +0,0 @@ -#include "note_c_hooks.h" - -#include -#include - -static const size_t REQUEST_HEADER_SIZE = 2; - -const struct device *i2c_dev = NULL; -bool i2c_initialized = false; - -uint32_t platform_millis(void) { - return (uint32_t)k_uptime_get(); -} - -void platform_delay(uint32_t ms) { - k_msleep(ms); -} - -const char *note_i2c_receive(uint16_t device_address_, uint8_t *buffer_, uint16_t size_, uint32_t *available_) { - // Let the Notecard know that we are getting ready to read some data - uint8_t size_buf[2]; - size_buf[0] = 0; - size_buf[1] = (uint8_t)size_; - uint8_t write_result = i2c_write(i2c_dev, size_buf, sizeof(size_buf), device_address_); - - if (write_result != 0) { - return "i2c: Unable to initate read from the Notecard\n"; - } - - // Read from the Notecard and copy the response bytes into the response buffer - const int request_length = size_ + REQUEST_HEADER_SIZE; - uint8_t read_buf[256]; - uint8_t read_result = i2c_read(i2c_dev, read_buf, request_length, device_address_); - - if (read_result != 0) { - return "i2c: Unable to receive data from the Notecard.\n"; - } else { - *available_ = (uint32_t)read_buf[0]; - uint8_t bytes_to_read = read_buf[1]; - for (size_t i = 0; i < bytes_to_read; i++) { - buffer_[i] = read_buf[i + 2]; - } - - return NULL; - } -} - -bool note_i2c_reset(uint16_t device_address_) { - (void)device_address_; - - if (i2c_initialized) { - return true; - } - - if (!i2c_dev) { - i2c_dev = DEVICE_DT_GET(DT_NODELABEL(i2c1)); - } - - if (!device_is_ready(i2c_dev)) { - printk("i2c: Device is not ready.\n"); - return false; - } - - printk("i2c: Device is ready.\n"); - - i2c_initialized = true; - return true; -} - -const char *note_i2c_transmit(uint16_t device_address_, uint8_t *buffer_, uint16_t size_) { - // Create a buffer that contains the number of bytes and the data to write to the Notecard - uint8_t write_buf[size_ + 1]; - write_buf[0] = (uint8_t)size_; - for (size_t i = 0; i < size_; i++) { - write_buf[i + 1] = buffer_[i]; - } - - // Write the message - uint8_t write_result = i2c_write(i2c_dev, write_buf, sizeof(write_buf), device_address_); - - if (write_result != 0) { - return "i2c: Unable to transmit data to the Notecard\n"; - } else { - return NULL; - } -} - -size_t note_log_print(const char *message_) { - if (message_) { - printk("%s", message_); - return 1; - } - - return 0; -} diff --git a/west.yml b/west.yml new file mode 100644 index 0000000..7c425c1 --- /dev/null +++ b/west.yml @@ -0,0 +1,19 @@ +manifest: + defaults: + revision: main + + remotes: + - name: zephyr + url-base: https://github.com/zephyrproject-rtos/ + + projects: + - name: zephyr + remote: zephyr + path: zephyr + revision: v3.5.0 + import: + # By using name-allowlist we can clone only the modules that are + # strictly needed by the application. + name-allowlist: + - cmsis # required by the ARM port + - hal_stm32 # required by the Swan MCU board (STM32 based) diff --git a/zephyr/module.yml b/zephyr/module.yml new file mode 100644 index 0000000..e5a07cb --- /dev/null +++ b/zephyr/module.yml @@ -0,0 +1,3 @@ +build: + cmake: . + kconfig: KConfig