Repository: mcknly/breadboard-os
Branch: main
Commit: 354d69e454e7
Files: 73
Total size: 317.7 KB
Directory structure:
gitextract_w576021d/
├── .gitignore
├── .gitmodules
├── CMakeLists.txt
├── LICENSE
├── README.md
├── cli/
│ ├── CMakeLists.txt
│ ├── cli_utils.c
│ ├── cli_utils.h
│ ├── motd.h
│ ├── node_bin.c
│ ├── node_dev.c
│ ├── node_etc.c
│ ├── node_lib.c
│ ├── node_mnt.c
│ ├── node_net.c
│ ├── node_proc.c
│ ├── node_root.c
│ ├── shell.c
│ ├── shell.h
│ └── shell_cmd.c
├── driver_lib/
│ ├── CMakeLists.txt
│ ├── bme280.c
│ ├── device_drivers.c
│ ├── device_drivers.h
│ └── mcp4725.c
├── hardware/
│ └── rp2xxx/
│ ├── CMakeLists.txt
│ ├── hardware_config.c
│ ├── hardware_config.h
│ ├── hw_adc.c
│ ├── hw_clocks.c
│ ├── hw_cores.c
│ ├── hw_gpio.c
│ ├── hw_i2c.c
│ ├── hw_net.c
│ ├── hw_registers.c
│ ├── hw_reset.c
│ ├── hw_spi.c
│ ├── hw_uart.c
│ ├── hw_usb.c
│ ├── hw_versions.c
│ ├── hw_watchdog.c
│ ├── hw_wifi.c
│ ├── net_inc/
│ │ ├── httpd_content/
│ │ │ ├── 404.html
│ │ │ ├── index.shtml
│ │ │ └── test.shtml
│ │ ├── hw_net.h
│ │ ├── hw_wifi.h
│ │ └── lwipopts.h
│ ├── onboard_flash.c
│ ├── onboard_led.c
│ ├── pico_sdk_import.cmake
│ ├── prebuild.cmake
│ └── rtos_config.h
├── littlefs/
│ └── CMakeLists.txt
├── main.c
├── project.cmake
├── rtos/
│ ├── CMakeLists.txt
│ ├── FreeRTOSConfig.h
│ ├── rtos_utils.c
│ └── rtos_utils.h
├── services/
│ ├── CMakeLists.txt
│ ├── cli_service.c
│ ├── heartbeat_service.c
│ ├── netman_service.c
│ ├── service_queues.c
│ ├── service_queues.h
│ ├── services.c
│ ├── services.h
│ ├── storman_service.c
│ ├── taskman_service.c
│ ├── usb_service.c
│ └── watchdog_service.c
└── version.h
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
build
.vscode
.trunk
.idea/
================================================
FILE: .gitmodules
================================================
[submodule "microshell"]
path = microshell
url = https://github.com/mcknly/microshell.git
[submodule "littlefs/littlefs"]
path = littlefs/littlefs
url = https://github.com/littlefs-project/littlefs.git
[submodule "cmake-git-version-tracking"]
path = git_version
url = https://github.com/andrew-hardin/cmake-git-version-tracking.git
================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.18)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
# Project/hardware-specific setup - set platform in project.cmake
# Platform-specific prebuild.cmake contains more information about boards
include(project.cmake)
set(hardware_dir ${CMAKE_CURRENT_SOURCE_DIR}/hardware/${PLATFORM})
if(EXISTS ${hardware_dir}/prebuild.cmake)
include(${hardware_dir}/prebuild.cmake)
endif()
project(${PROJ_NAME} C CXX ASM)
add_definitions(-DPROJECT_NAME=${PROJ_NAME}) # pass the project name to the preprocessor for use in the code
add_definitions(-DPROJECT_VERSION=${PROJ_VER}) # pass the project version number to the preprocessor for use in the code
add_definitions(-DCLI_USE_USB=${CLI_IFACE}) # 0: use UART for CLI (default), 1: use USB for CLI
add_definitions(-DBOARD=${BOARD}) # pass the board type to the preprocessor for further decision making
#add_definitions(-DSCHED_TEST_DELAY) # uncomment to force delay tasks for scheduler testing, see 'task_sched_update()' function
# FreeRTOS kernel import (platform-specific)
include($ENV{FREERTOS_KERNEL_PATH}/${freertos_port_path})
# add cli/microshell library subdirectory (submodule)
add_subdirectory(${PROJECT_SOURCE_DIR}/cli cli)
# add littlefs library subdirectory (submodule)
add_subdirectory(${PROJECT_SOURCE_DIR}/littlefs littlefs)
# add git version library subdirectory (submodule)
add_subdirectory(${PROJECT_SOURCE_DIR}/git_version cmake_git_version_tracking)
# create the main executable
add_executable(${PROJ_NAME}
main.c
)
target_link_libraries(${PROJ_NAME} PUBLIC
FreeRTOS-Kernel
FreeRTOS-Kernel-Heap4
cli
littlefs
cmake_git_version_tracking
${hardware_libs}
)
target_include_directories(${PROJ_NAME} PUBLIC
${PROJECT_SOURCE_DIR}
${PROJECT_SOURCE_DIR}/rtos
${PROJECT_SOURCE_DIR}/services
${PROJECT_SOURCE_DIR}/driver_lib
${PROJECT_SOURCE_DIR}/littlefs/littlefs
${PROJECT_SOURCE_DIR}/git_version
${hardware_includes}
)
# add additional source directories that are not libraries/submodules
add_subdirectory(${PROJECT_SOURCE_DIR}/rtos)
add_subdirectory(${PROJECT_SOURCE_DIR}/services)
add_subdirectory(${PROJECT_SOURCE_DIR}/driver_lib)
if(DEFINED hardware_dir)
add_subdirectory(${hardware_dir})
endif()
# add any additional definitions, options
target_compile_definitions(${PROJ_NAME} PUBLIC
CFG_TUSB_CONFIG_FILE="hardware_config.h" # override standard TinyUSB config file location
)
# hardware-specific build options, function defined in hardware/[platform]/CMakeLists.txt
if(COMMAND hardware_build_extra)
cmake_language(CALL hardware_build_extra)
endif()
# global compiler options for the project
target_compile_options(
${PROJ_NAME}
PRIVATE
-Werror -g -O0
)
# TODO: try to add -Wall and -Wextra to compile options to clean up more warnings
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# [ B r e a d b o a r d O S ]
```
___ ___
_____ _____ / /\ / /\
/ /::\ / /::\ / /::\ / /:/_
/ /:/\:\ / /:/\:\ / /:/\:\ / /:/ /\
/ /:/~/::\ / /:/~/::\ / /:/ \:\ / /:/ /::\
/__/:/ /:/\:/__/:/ /:/\:/__/:/ \__\:\/__/:/ /:/\:\
\ \:\/:/~/:| \:\/:/~/:| \:\ / /:/\ \:\/:/~/:/
\ \::/ /:/ \ \::/ /:/ \ \:\ /:/ \ \::/ /:/
\ \:\/:/ \ \:\/:/ \ \:\/:/ \__\/ /:/
\ \::/ \ \::/ \ \::/ /__/:/
\__\/ \__\/ \__\/ \__\/
```
**BreadboardOS** (BBOS, if you like) - as its namesake implies - is a firmware platform aimed at quick prototyping. BBOS was built around the following principles:
1. **_Every project should start with a CLI._** A command line interface makes prototyping, debugging, and testing about 1000% easier, especially over the lifespan of multiple projects with re-used pieces.
2. **_Write it once._** Don't waste time writing and re-writing the basic infrastructure for your projects. Modularize, duplicate, and profit!
3. **_Get running QUICKLY._** The faster you can bring up a project, the faster you can find out that you need to re-tool the whole dang thing. _But at least you don't have to write a CLI again!_
## Functional Description
BreadboardOS is built on top of FreeRTOS, enabling fast integration of new functional blocks and allowing for task concurrency without breaking the base system. Existing knowledge of RTOS is not necessarily required.
The central component of BBOS is the fantastic [microshell](https://microshell.pl/) project, which provides CLI functionality. Currently, a [fork](https://github.com/mcknly/microshell) of microshell is used, which includes some additional customization. The CLI implementation is organized into POSIX-style folders/files providing a recognizable user-interface for interacting with MCU hardware.
As of the v0.4 release, BBOS is implemented on a single MCU family - the Raspberry Pi RP2xxx (Pico, Pico W, Pico2, etc). However, the project has been structured such that all hardware-specific code resides in a single directory, with a header file providing HAL functionality. The platform was built with porting in mind.
### _Notable Features_
- Rich set of informational tools for checking system resources on the fly, a'la: `ps top free df`
- RTOS task manager for dynamically controlling system services at runtime
- Onboard flash filesystem with wear-leveling (thanks to [littlefs](https://github.com/littlefs-project/littlefs))
- Selective peripheral hardware initialization routines
- Interacting with chip I/O and serial buses directly from command line
- Watchdog service for system failsafe recovery
- Command history using arrows or ctrl-a/ctrl-z
- WiFi networking support with HTTP server for project dashboard
### _Demo_
As they say, a Youtube video is worth 10^6 words.
[https://www.youtube.com/watch?v=fQiYE_wlPt0](https://www.youtube.com/watch?v=fQiYE_wlPt0)
### _In The Media_
## Getting started
The following build instructions for BreadboardOS assume a GNU/Linux environment, or alternatively WSL under Windows. Building in other environments may be possible but is not covered here.
### Prerequisites
- A basic embedded ARM development enviroment: `build-essential gcc-arm-none-eabi newlib-arm-none-eabi cmake`
- The [FreeRTOS Kernel](https://github.com/FreeRTOS/FreeRTOS-Kernel) in an accessible location on the system
- The Raspberry Pi [Pico SDK](https://github.com/raspberrypi/pico-sdk) in an accessible location on the system
Ensure that the environment variables `$FREERTOS_KERNEL_PATH` and `$PICO_SDK_PATH` are set and contain the absolute paths to those projects:
`export FREERTOS_KERNEL_PATH=/path/to/freertos/kernel`
`export PICO_SDK_PATH=/path/to/pico/sdk`
It is suggested to add these commands to the user's `~/.profile`, `~/.bashrc`, etc depending on the system.
**NOTE for RP2350**: Please ensure that you pull the latest mainline versions of `pico-sdk` and `FreeRTOS-Kernel`, as fixes/support has been recently added (as of early `25).
### Setting up BreadboardOS
- Clone the repository: `git clone https://github.com/mcknly/breadboard-os.git`
- Enter the project folder: `cd breadboard-os`
- Pull in submodules: `git submodule update --init` (also do this for pico-sdk and FreeRTOS-Kernel!)
Open `project.cmake` and edit the following parameters:
```
# PROJECT NAME - in quotes, no spaces
set(PROJ_NAME "my-bbos-proj")
# PROJECT VERSION - in quotes, no spaces, can contain alphanumeric if necessary
set(PROJ_VER "0.0")
# CLI INTERFACE - 0: use UART for CLI (default), 1: use USB for CLI
set(CLI_IFACE 0)
# MCU PLATFORM - set the MCU platform being used (i.e. the subdir in 'hardware/')
set(PLATFORM rp2xxx)
# BOARD - set the board being used (platform-specific prebuild.cmake contains more information about boards)
set(BOARD pico2)
# HOSTNAME - hostname will be shown at CLI prompt, and used for network connections
set(HOSTNAME "bbos")
```
Using CLI over USB requires no additional hardware; using CLI over UART will require a USB-UART adapter (i.e. FTDI FT232 or SiLabs CP2102). Using CLI/UART enables some additional early boot status prints.
### Building
- Create & enter build folder: `mkdir build && cd build`
- Generate the build files with cmake: `cmake ..` (note: this will default to a 'Release' build)
- Compile the project: `make -j4` (# cores can be altered)
This should generate firmware image formats `.bin`, `.hex`, `.uf2`, to be programmed to flash via your preferred method.
### Debugging
For a full debugging-enabled IDE-like experience (recommended), using MS VS Code plus the `C/C++`, `CMake`, and `Cortex-Debug` extensions with your debug probe of choice (Pi Debug Probe, J-Link, etc) is the widely preferred method. See [this](https://www.digikey.com/en/maker/projects/raspberry-pi-pico-and-rp2040-cc-part-2-debugging-with-vs-code/470abc7efb07432b82c95f6f67f184c0) great tutorial by DigiKey.
## Usage - building your own application
Creating your own application on the BreadboardOS platform entails adding one or more new "services" - which are essentially standalone FreeRTOS tasks. An example of this is given in the `services/heartbeat_service.c` file, the structure of which can be used as a basis for your new service/application. See `services/services.h` which includes extensive documentation on how services/tasks are implemented on BBOS.
To understand how your new service can interact with the system, refer to the function implementations in the file nodes within the `cli` directory - these have been provided as a test bed for the underlying system functionality, and the intention is for any new services/tasks to also have their functions mapped into new CLI folders & files - think of this as a test menu system for your project. The CLI could also be leveraged as an automated production test interface!
## Code Organization & Technical Detail
Project directory structure:
- `cli/` - directory/file structure for the CLI, and interface to the microshell submodule
- `driver_lib/` - drivers for external peripheral hardware - library to grow over time
- `hardware/` - all hardware-specific (HAL) functional implementation, each MCU platform has its own subdirectory
- `littlefs/` - contains the 'littlefs' submodule and CMake wrapper
- `rtos/` - FreeRTOS interface & configuration files
- `services/` - where the main application elements (FreeRTOS tasks) reside
- `git_version/` - submodule for adding GIT repo metadata into the binary
- `microshell/` - submodule for microshell
## Contributing
Contributions to the project are welcome! Here are some specific ways to contribute:
- Use the project! build cool things, identify bugs, submit issues
- Add additional peripheral hardware drivers to `driver_lib`
- Create useful utility services
- Port the project to a new MCU platform
To contribute - fork the project, make enhancements, submit a PR.
## Acknowledgements
BreadboardOS is built in no small part upon the substantial work of others:
[microshell](https://github.com/marcinbor85/microshell)
[littlefs](https://github.com/littlefs-project/littlefs)
[cmake-git-version-tracking](https://github.com/andrew-hardin/cmake-git-version-tracking)
[freertos](https://github.com/FreeRTOS/FreeRTOS-Kernel)
[pico-sdk](https://github.com/raspberrypi/pico-sdk)
## License
This project is released under the [MIT License](https://opensource.org/license/mit/).
In lieu of including the full license text in every source file, the following tag is used:
`// SPDX-License-Identifier: MIT`
---
---
it's fresh
================================================
FILE: cli/CMakeLists.txt
================================================
# This file configures the build for the microshell submodule, which is included
# as source for the CLI which will be built as a static library to be used by
# the main application.
# pull in microshell setup
add_subdirectory(${PROJECT_SOURCE_DIR}/microshell/src microshell)
# run microshell build setup script
build_ush(microshell)
# defines to use in microshell build
target_compile_definitions(
microshell
PUBLIC
PICO
)
# create the source file list for the CLI library
set(cli_sources
${CMAKE_CURRENT_LIST_DIR}/shell.c
${CMAKE_CURRENT_LIST_DIR}/shell_cmd.c
${CMAKE_CURRENT_LIST_DIR}/cli_utils.c
${CMAKE_CURRENT_LIST_DIR}/node_root.c
${CMAKE_CURRENT_LIST_DIR}/node_dev.c
${CMAKE_CURRENT_LIST_DIR}/node_bin.c
${CMAKE_CURRENT_LIST_DIR}/node_proc.c
${CMAKE_CURRENT_LIST_DIR}/node_mnt.c
${CMAKE_CURRENT_LIST_DIR}/node_etc.c
${CMAKE_CURRENT_LIST_DIR}/node_lib.c
)
if (ENABLE_WIFI)
list(APPEND cli_sources
${CMAKE_CURRENT_LIST_DIR}/node_net.c
)
endif()
# create cli library to use at top level (linked against microshell)
add_library(cli STATIC
${cli_sources}
)
target_link_libraries(cli PUBLIC
microshell
FreeRTOS-Kernel
cmake_git_version_tracking
${hardware_libs}
)
target_include_directories(cli PUBLIC
${CMAKE_CURRENT_LIST_DIR}
${PROJECT_SOURCE_DIR}
${PROJECT_SOURCE_DIR}/rtos
${PROJECT_SOURCE_DIR}/driver_lib
${PROJECT_SOURCE_DIR}/services
${PROJECT_SOURCE_DIR}/littlefs/littlefs
${PROJECT_SOURCE_DIR}/git_version
${hardware_includes}
)
# add any additional definitions, options
target_compile_definitions(cli PUBLIC
CFG_TUSB_CONFIG_FILE="hardware_config.h" # override standard TinyUSB config file location
)
if(ENABLE_MOTD)
target_compile_definitions(cli PUBLIC -DPRINT_MOTD_AT_BOOT)
endif()
target_compile_options(
cli
PRIVATE
-Werror -g -O0
)
# TODO: try to add -Wall and -Wextra to compile options to clean up more warnings
================================================
FILE: cli/cli_utils.c
================================================
/******************************************************************************
* @file cli_utils.c
*
* @brief Utility functions to use for CLI interaction
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include
#include "shell.h"
#include "cli_utils.h"
#include "FreeRTOS.h"
size_t hex_string_to_byte_array(char *hex_string, uint8_t *byte_array) {
if (hex_string[0] != '0' || hex_string[1] != 'x') {
return 0; // error if no "0x" prefix
}
int len = strlen(hex_string + 2); // hex string length minus the prefix
if (len % 2 != 0) {
return 0; // error if not an even number of full bytes
}
int string_index, byte_index;
for (string_index = 0, byte_index = 0; byte_index < len / 2; string_index += 2, byte_index++) {
// craaaaaazy long if to check for characters in the range 0-9, a-f, or A-F
if (((((hex_string[string_index + 2] - 48) <= 9) && ((hex_string[string_index + 2] - 48) >= 0)) || // first character 0-9?...or
(((hex_string[string_index + 2] - 65) <= 5) && ((hex_string[string_index + 2] - 65) >= 0)) || // first character A-F?...or
(((hex_string[string_index + 2] - 97) <= 5) && ((hex_string[string_index + 2] - 97) >= 0))) // first character a-f?
&& // plus
((((hex_string[string_index + 3] - 48) <= 9) && ((hex_string[string_index + 3] - 48) >= 0)) || // second character 0-9?...or
(((hex_string[string_index + 3] - 65) <= 5) && ((hex_string[string_index + 3] - 65) >= 0)) || // second character A-F?...or
(((hex_string[string_index + 3] - 97) <= 5) && ((hex_string[string_index + 3] - 97) >= 0)))) // second character a-f?
// whitespace for the win!
{
// prepare yourself for this wizardry... convert 2-char ASCII hex to decimal integer
// this works due to some clever spacing between 0, A, and a in the ASCII table.
// thanks to: https://gist.github.com/xsleonard/7341172 for the tip
byte_array[byte_index] = (hex_string[string_index + 2] % 32 + 9) % 25 * 16 + (hex_string[string_index + 3] % 32 + 9) % 25;
}
else {return 0;} // error if not a hexadecimal value
}
return byte_index;
}
void print_motd(void) {
// global Message of the Day stored in motd.h. Declared here and defined in the header (violates best practice, sorry y'all)
extern const char *motd_ascii;
// print directly from MOTD message stored in flash
shell_print_slow(motd_ascii);
}
================================================
FILE: cli/cli_utils.h
================================================
/******************************************************************************
* @file cli_utils.h
*
* @brief Utility functions to use for CLI interaction
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#ifndef CLI_UTILS_H
#define CLI_UTILS_H
#include
#include
/**
* @brief Convert a string of hex values to an integer byte array.
*
* Takes an ASCII string (prefixed with "0x") and converts each hex-digit pair
* into its 8-bit integer equivalent, then appends into an array of bytes. The
* function will check for valid hexadecimal format, returning either error or
* the length of the array (number of bytes). Note that there must be an even
* number of hex characters (full bytes).
*
* @param hex_string pointer to the hex string char array
* @param byte_array pointer to the location to store the resulting byte array
*
* @return length of the resulting byte array (number of bytes > 0) if success,
* 0 if formatting error on input string
*/
size_t hex_string_to_byte_array(char *hex_string, uint8_t *byte_array);
/**
* @brief Print the Message of the Day (MOTD)
*
* Calling this function will print the MOTD out to the CLI using an advanced
* Graphical Ascii Slow Processing (GASP!) algorithm.
*
* @param none
*
* @return nothing
*/
void print_motd(void);
#endif /* CLI_UTILS_H */
================================================
FILE: cli/motd.h
================================================
/******************************************************************************
* @file motd.h
*
* @brief contains the ASCII string (or art!) for MOTD (message of the day)
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#ifndef MOTD_H
#define MOTD_H
#include
#include "version.h"
// stringify helper - use xstr() to convert #define to a usable string
#define str(s) # s
#define xstr(s) str(s)
// place motd message in motd_ascii character array pointer
// thanks to:
// great ASCII art generator - https://github.com/TheZoraiz/ascii-image-converter
// great ASCII font generator - https://patorjk.com/software/taag
//
// remember...any forward slashes need to be escaped by an extra /
// also make sure there are no quotes in the art, replace them with some other char
//
// the image below relies on "braille" characters (UTF-8/Unicode) and requires a suitable
// terminal font to render properly, specifically support for characters U+2800..U+28FF.
const char *motd_ascii =
"\r\n"
USH_SHELL_FONT_COLOR_YELLOW
" ⣿⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⡠⠤⢤⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⢀⠤⠐⠒⠊⢉⣉⣉⣉⣉⠁⠀⠀⠀⡀⠀⠠⠄⣉⠑⠒⠤⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⢀⠊⢁⡠⢔⣚⣭⡵⠒⠀⠈⠶⠭⠑⢒⡦⢄⠉⠲⡢⢄⠉⠲⡢⣄⡈⠑⠢⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⡐⢁⠔⠕⠘⠻⢛⣻⣿⣀⠀⠀⠀⠀⠀⠀⠀⠑⠅⡢⡈⠑⢍⢄⠈⢄⢮⣕⡢⣀⠑⠢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⢰⠀⠆⣶⣄⣰⣿⣿⣿⡿⠿⠁⠀⠀⠀⠀⢀⣀⣀⣀⣈⠊⢕⢄⠑⠂⠈⣂⣀⣀⣀⣉⣢⣌⠑⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠸⡀⠑⢝⠿⣿⣿⣿⣷⣦⣀⣀⣀⡀⢸⣿⣿⠿⢟⡫⠭⠒⠒⢉⣉⠤⠤⠤⠤⠤⠤⠤⢄⣈⣁⠀⠑⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⢀⣀⣐⡱⠀⠀⠑⠪⣛⠿⣿⣿⣿⣿⣿⣿⡮⡫⠕⢉⣡⠤⣖⣒⣭⣵⣶⣿⣿⣿⣿⣿⣿⣿⣷⣶⣶⡍⡂⠘⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⠒⢉⣉⣁⠤⠤⠤⢤⠀⢄⣀⠀⠉⠒⠭⡻⢿⡿⢋⠊⡠⢒⣵⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⣿⣿⣿⡇⡃⢠⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⠀⡈⠢⡲⣿⣿⣿⢸⠀⢰⢰⣕⣒⠄⡀⠈⠑⠊⠁⢐⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣎⠛⠟⠔⠀⡊⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⡄⢨⠢⡈⠪⡻⣿⣜⢀⠐⠬⡻⢿⣿⣮⣕⢄⡀⠀⢸⢸⣿⣿⣿⣿⠿⠿⠿⠿⠿⠿⠛⠛⠃⡀⣸⣿⣿⣾⠞⠂⠐⠤⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣔⢄⠑⢌⠢⡈⢊⠻⣷⣅⡢⣈⠑⠭⡻⢿⣷⡎⡆⠨⡘⣅⡀⠁⠀⢀⣠⣤⠤⠴⢶⣒⣛⡫⠭⠭⠕⠒⠒⠊⢀⣉⣀⣀⣀⣈⡉⠒⠢⢄⣀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠁⠑⠄⠑⢌⠢⡑⢌⠻⣿⣶⣕⠤⡈⠑⠍⡣⢣⠀⠇⡿⡫⠭⠭⠒⠒⠒⢉⣉⣁⡠⠤⠤⢔⣒⣒⣺⣭⣭⣵⣶⣶⣶⣶⣶⣮⣭⣖⡦⢄⣉⡉⠒⠒⠦⡀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠪⡢⡀⠠⡈⢂⠑⢜⢿⣿⣿⣮⣒⢄⡈⠑⠀⢉⠊⠀⠶⣒⣒⠭⢭⡽⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⢗⣒⡬⠭⠓⠂⢰⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠈⢂⠈⠢⡑⢄⠑⢝⢿⣿⣿⣷⣮⡲⢄⣀⡀⠀⠀⠀⠀⠉⠑⡙⢿⣿⣿⣿⣿⣿⣿⣿⠿⣛⣛⣛⠭⠭⠭⠭⠗⠒⠒⠋⢉⣁⣀⠤⠔⠒⠀⡸⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠐⠕⢄⠀⢄⠑⢄⠑⠝⢿⣿⣿⣿⣷⣶⣔⢄⡀⠀⠀⠀⠀⠀⠉⠒⠺⠭⠕⠒⠒⠒⠉⠤⠤⠤⡀⠒⠒⣒⠒⠊⣭⡉⠁⢀⣀⡠⠤⠊⡠⣈⠑⠤⡻ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠁⡀⠑⢄⠑⡀⠢⡙⣿⣿⣿⣿⣿⣷⣮⣖⡢⢄⠀⠀⠀⠒⠒⠒⠒⠒⠉⠍⢄⡑⠪⠥⠤⠀⠑⠻⠵⠒⠒⠋⢉⣁⣠⠤⠔⠊⢞⣒⣑⠢⠈ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠈⠪⡢⡀⠠⡨⡢⡈⠪⡻⣿⣿⣿⣿⣿⣿⣿⣷⣕⢄⡀⠑⠒⠒⠒⠒⠒⠒⠊⢉⣀⠠⠔⣒⣒⣒⡲⠦⠭⠭⠓⠒⠒⠊⢉⣉⣁⣠⠤⠤⠀ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢈⢄⠈⠪⡪⡢⡈⠪⡻⣿⣿⣿⣿⣿⡿⠿⠷⢎⣉⣑⡲⠶⠶⠟⠛⠛⠋⢉⣉⣉⣀⠤⠤⠤⠔⠒⠒⠒⠉⠉⠉⠁⠀⠰⠾⢟⡐⢀ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⠕⢄⠐⢌⢈⠢⡈⠪⠭⠝⠒⠒⠊⢉⣉⣁⡠⠤⠤⢔⣒⣒⣪⣭⣭⣽⣶⣶⠦⠤⢄⣀⣀⡠⠤⠤⠴⠒⠒⠒⠉⢉⣉⣁⡠⢬ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡡⡀⠑⠙⢨⠀⠀⡒⣪⣭⣭⣵⣶⣶⣾⡿⠿⢟⣛⣛⡭⠭⠭⠒⠒⠚⢉⣉⣁⡠⠤⠤⠤⠒⠒⠒⠉⠉⠉⠁⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠪⡢⡈⠪⡀⠀⢑⣛⣛⡯⠭⠽⠒⠒⠊⣉⣉⡠⠤⠤⠖⠒⠒⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠪⠢⢄⣀⣀⡠⠤⠤⠖⠒⠚⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿ \r\n"
" ⣿⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣿ \r\n"
USH_SHELL_FONT_STYLE_RESET;
// the bbos ascii header is seperate from MOTD so we can choose to print just one.
// it doesn't look right here because of the extra '\' escapes, but will magically
// look great in the terminal.
const char *bbos_header_ascii =
USH_SHELL_FONT_COLOR_YELLOW
" ___ ___ \r\n"
" _____ _____ / /\\ / /\\ \r\n"
" / /::\\ / /::\\ / /::\\ / /:/_ \r\n"
" / /:/\\:\\ / /:/\\:\\ / /:/\\:\\ / /:/ /\\ \r\n"
" / /:/~/::\\ / /:/~/::\\ / /:/ \\:\\ / /:/ /::\\ \r\n"
" /__/:/ /:/\\:/__/:/ /:/\\:/__/:/ \\__\\:\\/__/:/ /:/\\:\\ \r\n"
" \\ \\:\\/:/~/:| \\:\\/:/~/:| \\:\\ / /:/\\ \\:\\/:/~/:/ \r\n"
" \\ \\::/ /:/ \\ \\::/ /:/ \\ \\:\\ /:/ \\ \\::/ /:/ \r\n"
" \\ \\:\\/:/ \\ \\:\\/:/ \\ \\:\\/:/ \\__\\/ /:/ \r\n"
" \\ \\::/ \\ \\::/ \\ \\::/ /__/:/ \r\n"
" \\__\\/ \\__\\/ \\__\\/ \\__\\/ "
xstr(BBOS_VERSION_MAJOR) "." xstr(BBOS_VERSION_MINOR) // add version number to header
" " // add an extra space that will be replaced by BBOS_VERSION_MOD
"\r\n"
"\r\n"
USH_SHELL_FONT_COLOR_MAGENTA
USH_SHELL_FONT_STYLE_BOLD
" [["
USH_SHELL_FONT_COLOR_CYAN
" Welcome to BreadboardOS! "
USH_SHELL_FONT_COLOR_MAGENTA
"]]\r\n"
"\r\n"
USH_SHELL_FONT_STYLE_RESET;
#endif /* MOTD_H */
================================================
FILE: cli/node_bin.c
================================================
/******************************************************************************
* @file node_bin.c
*
* @brief /bin folder for the CLI, contains system executables
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include
#include
#include "hardware_config.h"
#include "shell.h"
#include "services.h"
#include "service_queues.h"
#include "FreeRTOS.h"
#include "FreeRTOSConfig.h"
#include "task.h"
/**
* @brief '/bin/ps' executable callback function.
*
* Prints out FreeRTOS task list information in a similar style to *nix 'ps'.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void ps_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
// initialize array to hold task list ASCII output. Assume 40 bytes per task + header
const char *tasks_header = USH_SHELL_FONT_STYLE_BOLD
USH_SHELL_FONT_COLOR_BLUE
" Min\r\n"
"Task State Pri Stack No\r\n"
"------------------------------------------\r\n"
USH_SHELL_FONT_STYLE_RESET;
int header_len = strlen(tasks_header);
int tasks_maxlen = 40 * uxTaskGetNumberOfTasks();
char *ps_msg = pvPortMalloc(header_len + tasks_maxlen);
strcpy(ps_msg, tasks_header);
// call FreeRTOS vTaskList API and print to CLI
vTaskListTasks(ps_msg + header_len, tasks_maxlen); // note this is a blocking, processor intensive function
shell_print(ps_msg);
vPortFree(ps_msg);
}
/**
* @brief '/bin/top' executable callback function.
*
* Prints out FreeRTOS task runtime stats information in a similar style to *nix 'top'.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void top_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
// initialize char array to hold task stats ASCII output. Assume 40 bytes per task + header
const char *tasks_header = USH_SHELL_FONT_STYLE_BOLD
USH_SHELL_FONT_COLOR_BLUE
"Task Runtime(us) Percentage\r\n"
"------------------------------------------\r\n"
USH_SHELL_FONT_STYLE_RESET;
int header_len = strlen(tasks_header);
int tasks_maxlen = 40 * uxTaskGetNumberOfTasks();
char *top_msg = pvPortMalloc(header_len + tasks_maxlen);
strcpy(top_msg, tasks_header);
// call FreeRTOS vTaskGetRunTimeStats API and print to CLI
vTaskGetRunTimeStatistics(top_msg + header_len, tasks_maxlen);
shell_print(top_msg);
vPortFree(top_msg);
}
/**
* @brief '/bin/free' executable callback function.
*
* Prints out the FreeRTOS heap memory manager usage statistics in a similar style to *nix 'free'.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void free_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
HeapStats_t *heap_stats = pvPortMalloc(sizeof(HeapStats_t)); // structure to hold heap stats results
vPortGetHeapStats(heap_stats); // get the heap stats
const int heap_stats_maxlen = 300; // just a rough estimate for dynamic mem needed
char *heap_stats_msg = pvPortMalloc(heap_stats_maxlen);
unsigned int total_heap_size = configTOTAL_HEAP_SIZE;
// format the heap stats
snprintf(heap_stats_msg, heap_stats_maxlen,
USH_SHELL_FONT_STYLE_BOLD
USH_SHELL_FONT_COLOR_BLUE
"Memory Statistics Bytes\r\n"
"------------------------------\r\n"
USH_SHELL_FONT_STYLE_RESET
"Total heap:\t\t%u\r\n"
"Used heap:\t\t%u\r\n"
"Available heap:\t\t%u\r\n"
"Largest free block:\t%u\r\n"
"Smallest free block:\t%u\r\n"
"Num free blocks:\t%u\r\n"
"Min ever heap:\t\t%u\r\n"
"Num mallocs:\t\t%u\r\n"
"Num frees:\t\t%u\r\n",
total_heap_size,
(total_heap_size - heap_stats->xAvailableHeapSpaceInBytes),
heap_stats->xAvailableHeapSpaceInBytes,
heap_stats->xSizeOfLargestFreeBlockInBytes,
heap_stats->xSizeOfSmallestFreeBlockInBytes,
heap_stats->xNumberOfFreeBlocks,
heap_stats->xMinimumEverFreeBytesRemaining,
heap_stats->xNumberOfSuccessfulAllocations,
heap_stats->xNumberOfSuccessfulFrees
);
shell_print(heap_stats_msg);
vPortFree(heap_stats);
vPortFree(heap_stats_msg);
}
/**
* @brief '/bin/df' executable callback function.
*
* Prints out the internal/onboard (program) flash memory usage in a similar style to *nix 'df'.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void df_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
flash_usage_t flash_usage;
const int flash_usage_msg_maxlen = 300;
char *flash_usage_msg = pvPortMalloc(flash_usage_msg_maxlen);
// get the flash usage data struct
flash_usage = onboard_flash_usage();
// format the flash usage printout
snprintf(flash_usage_msg, flash_usage_msg_maxlen,
USH_SHELL_FONT_STYLE_BOLD
USH_SHELL_FONT_COLOR_BLUE
"Flash Statistics KBytes\r\n"
"--------------------------------------\r\n"
USH_SHELL_FONT_STYLE_RESET
"Total flash size:\t\t%u\r\n"
"Program binary size:\t\t%u\r\n"
"Filesystem reserved size:\t%u\r\n"
"Free flash space:\t\t%u\r\n",
flash_usage.flash_total_size / 1024,
flash_usage.program_used_size / 1024,
flash_usage.fs_reserved_size / 1024,
flash_usage.flash_free_size / 1024
);
shell_print(flash_usage_msg);
vPortFree(flash_usage_msg);
}
/**
* @brief '/bin/kill' executable callback function.
*
* Kill an RTOS task using the name given by 'ps'.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void kill_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
static char err_msg[100]; // buffer for holding the error output message
if (argc == 2) {
struct taskman_item_t tmi;
tmi.task = xTaskGetHandle(argv[1]);
tmi.action = DELETE;
// put the kill request into the taskmanager queue
if (tmi.task != NULL) {
taskman_request(&tmi);
}
else {
sprintf(err_msg, "%s is not a currently running task", argv[1]);
shell_print(err_msg);
}
}
else {
shell_print("command requires exactly one argument, see 'help '");
}
}
/**
* @brief '/bin/service' executable callback function.
*
* Interact with system services (list/start/suspend/resume). Services are defined in services.h
* Note that stopping services is performed with '/bin/kill'.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void service_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
char *err_msg = pvPortMalloc(100); // buffer for holding the error output message
char *service_msg = pvPortMalloc(32); // buffer for holding the service success message
if (argc == 3) {
struct taskman_item_t tmi;
tmi.task = xTaskGetHandle(argv[2]);
if (strcmp(argv[1], "start") == 0) { // START a service (task)
if (tmi.task != NULL) {
sprintf(err_msg, "%s is already running", argv[2]);
shell_print(err_msg);
}
else {
// Iterate through service_descriptors array to find the matching service index
int i;
for(i = 0; i < service_descriptors_length; i++) {
if (strcmp(argv[2], service_descriptors[i].name) == 0) {
service_descriptors[i].service_func(); // call the function pointer for the matching service
break;
}
if (i == (service_descriptors_length - 1)) {
sprintf(err_msg, "%s is not an available service, try 'service list'", argv[2]);
shell_print(err_msg);
break;
}
}
}
}
else if (strcmp(argv[1], "suspend") == 0) { // SUSPEND a running service (task)
if (tmi.task == NULL) {
sprintf(err_msg, "%s is not a running service, try '/bin/ps'", argv[2]);
shell_print(err_msg);
}
else {
tmi.action = SUSPEND;
taskman_request(&tmi);
sprintf(service_msg, "%s service suspended", argv[2]);
shell_print(service_msg);
}
}
else if (strcmp(argv[1], "resume") == 0 && tmi.task != NULL) { // RESUME a suspended service (task)
if (tmi.task == NULL) {
sprintf(err_msg, "%s is not a running service, try '/bin/ps'", argv[2]);
shell_print(err_msg);
}
else {
tmi.action = RESUME;
taskman_request(&tmi);
sprintf(service_msg, "%s service resumed", argv[2]);
shell_print(service_msg);
}
}
else {
shell_print("command syntax error, see 'help '");
}
}
else if (argc == 2 && strcmp(argv[1], "list") == 0) { // LIST available services and their current state
int i;
const char *service_list_header = USH_SHELL_FONT_STYLE_BOLD
USH_SHELL_FONT_COLOR_BLUE
"Available Services\tStatus\r\n"
"------------------------------------\r\n"
USH_SHELL_FONT_STYLE_RESET;
char *service_list_msg = pvPortMalloc(strlen(service_list_header) +
(service_descriptors_length *
(configMAX_TASK_NAME_LEN + 16))); // add 16 bytes per line for service state and whitespace
TaskHandle_t service_taskhandle;
char service_state[12];
strcpy(service_list_msg, service_list_header);
// interate through available services, get their states from RTOS
for (i = 0; i < service_descriptors_length; i++) {
service_taskhandle = xTaskGetHandle(service_descriptors[i].name);
if (service_taskhandle == NULL) {
strcpy(service_state, "not started");
}
else {
switch (eTaskGetState(service_taskhandle)) {
case (eRunning):
case (eBlocked):
strcpy(service_state, "running");
break;
case (eSuspended):
strcpy(service_state, "suspended");
break;
case (eReady):
strcpy(service_state, "not started");
break;
default:
break;
}
}
// copy current service name into table
strcpy(service_list_msg + strlen(service_list_msg), service_descriptors[i].name);
// add an extra tab to short service names to make the table look better
if (strlen(service_descriptors[i].name) < (configMAX_TASK_NAME_LEN - 8)) {
strcpy(service_list_msg + strlen(service_list_msg), "\t");
}
// copy current service state into table
sprintf(service_list_msg + strlen(service_list_msg), "\t\t%s\r\n", service_state);
}
shell_print(service_list_msg);
vPortFree(service_list_msg);
}
else {
shell_print("command syntax error, see 'help '");
}
// free heap used for the string buffers
vPortFree(err_msg);
vPortFree(service_msg);
}
/**
* @brief '/bin/reboot' executable callback function.
*
* Reboot the MCU by forcing a watchdog timeout.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void reboot_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
if (argc == 1) { // if there are no arguments, perform a normal watchdog reboot
shell_print("rebooting system...");
// stop the watchdog service so we can force a watchdog reboot
struct taskman_item_t tmi; // create a taskmanager action item
tmi.task = xTaskGetHandle("watchdog");
tmi.action = SUSPEND;
if (tmi.task != NULL) { // only try to suspend watchdog if it is actually running
taskman_request(&tmi); // put the action in taskmanager queue
// wait here until watchdog task state is "suspended"
while(eTaskGetState(tmi.task) != eSuspended) {}
}
// now we can force a watchdog reboot
force_watchdog_reboot();
}
else if (argc == 2 && strcmp(argv[1], "bootloader") == 0) { // reboot to bootloader
shell_print("rebooting to bootloader...");
wait_here_us(1E6); // wait for a second so user can see output
reset_to_bootloader();
}
else {
shell_print("command syntax error, see 'help '");
}
}
// bin directory files descriptor
static const struct ush_file_descriptor bin_files[] = {
{
.name = "ps", // file name (required)
.description = "print running service info", // optional file description
.help = NULL, // optional help manual
.exec = ps_exec_callback, // optional execute callback
.get_data = NULL, // optional get data (cat) callback
.set_data = NULL // optional set data (echo) callback
},
{
.name = "top",
.description = "print runtime stats for services",
.help = NULL,
.exec = top_exec_callback,
.get_data = NULL,
.set_data = NULL
},
{
.name = "free",
.description = "print heap memory (RAM) usage stats",
.help = NULL,
.exec = free_exec_callback,
.get_data = NULL,
.set_data = NULL
},
{
.name = "df",
.description = "print flash memory usage stats",
.help = NULL,
.exec = df_exec_callback,
.get_data = NULL,
.set_data = NULL
},
{
.name = "kill",
.description = "kill the service name given by 'bin/ps'",
.help = "usage: kill <\e[3mservicename\e[0m>\r\n",
.exec = kill_exec_callback,
.get_data = NULL,
.set_data = NULL
},
{
.name = "service",
.description = "interact with available services",
.help = "usage: service <\e[3mservicename\e[0m>\r\n",
.exec = service_exec_callback,
.get_data = NULL,
.set_data = NULL
},
{
.name = "reboot",
.description = "reboot device",
.help = "usage: reboot - normal mode\r\n"
" reboot bootloader - UF2 mode\r\n",
.exec = reboot_exec_callback,
.get_data = NULL,
.set_data = NULL
}
};
// bin directory handler
static struct ush_node_object bin;
void shell_bin_mount(void)
{
// mount bin directory
ush_node_mount(&ush, "/bin", &bin, bin_files, sizeof(bin_files) / sizeof(bin_files[0]));
}
================================================
FILE: cli/node_dev.c
================================================
/******************************************************************************
* @file node_dev.c
*
* @brief /dev folder for the CLI, contains hardware peripheral access functions
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include
#include
#include
#include
#include "hardware_config.h"
#include "shell.h"
#include "cli_utils.h"
#include "services.h"
#include "service_queues.h"
#include "lfs.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
/**
* @brief '/dev/led' get data callback function.
*
* Returns the current state of the onboard LED using the built-in 'cat' CLI command.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return length of pointer to the LED state data (0=OFF or 1=ON)
*/
size_t led_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
bool led_state;
static char led_state_msg[16];
if (onboard_led_get() == true) {
strcpy(led_state_msg, "LED STATE ON\r\n");
} else {
strcpy(led_state_msg, "LED STATE OFF\r\n");
}
// set pointer to data
*data = (uint8_t*)led_state_msg;
// return data size
return strlen(led_state_msg);
}
/**
* @brief '/dev/led' set data callback function.
*
* Sets the state of the onboard LED using the built-in 'echo' CLI command.
*
* @param ush_file_data_setter Params given by typedef ush_file_data_setter. see ush_types.h
*
* @return nothing
*/
void led_set_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t *data, size_t size)
{
if (strcmp(data, "0") == 0) {
onboard_led_set(0);
} else if (strcmp(data, "1") == 0) {
onboard_led_set(1);
} else {
shell_print("set value should be <0> or <1>");
}
}
/**
* @brief '/dev/time' get data callback function.
*
* Returns the microseconds value of the system timer using the built-in 'cat' CLI command.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return length of pointer to the timer value data
*/
size_t time_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
static char time_msg[57]; // text string plus 14 digits for timer value, ~3 years before rollover
// of course... the 64-bit timer value can count to 585 thousand years,
// in case we want to ever throw a couple more bytes at it for longetivity
snprintf(time_msg, 57, "current system timer value: %llu microseconds\r\n", get_time_us());
// set pointer to data
*data = (uint8_t*)time_msg;
// return data size
return strlen(time_msg);
}
/**
* @brief '/dev/gpio' executable callback function.
*
* Read/write value (0, 1) to individual GPIO pins. Note that the 'GPIO num'
* supplied to the command should match the GPIO_x_MCU_ID index number configured
* in hardware_config.h.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void gpio_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
bool syntax_err = false;
char *gpio_msg = pvPortMalloc(20);
if (strcmp(argv[1], "read") == 0 && argc == 3) {
int gpio_index = atoi(argv[2]);
// make sure the GPIO number provided is in range
if (gpio_index < GPIO_COUNT) {
sprintf(gpio_msg, "GPIO_%d value: %d", gpio_index, gpio_read_single(gpio_index));
}
else {
syntax_err = true;
shell_print("pin is not a configured GPIO");
}
}
else if (strcmp(argv[1], "write") == 0 && argc == 4) {
int gpio_index = atoi(argv[2]);
int gpio_val = atoi(argv[3]);
// make sure the value is 0 or 1
if (gpio_val == 0 || gpio_val == 1) {
// make sure the GPIO number provided is in range
if (gpio_index < GPIO_COUNT && gpio_settings.gpio_direction[gpio_index] == GPIO_OUT) {
gpio_write_single(gpio_index, gpio_val);
sprintf(gpio_msg, "GPIO_%d set to %d", gpio_index, gpio_read_single(gpio_index));
}
else {
syntax_err = true;
shell_print("pin is not a configured GPIO output");
}
}
else {
syntax_err = true;
shell_print("value must be 0 or 1");
}
}
else {
syntax_err = true;
shell_print("command syntax error, see 'help '");
}
if (!syntax_err) {
shell_print(gpio_msg);
}
vPortFree(gpio_msg);
}
/**
* @brief '/dev/gpio' get data callback function.
*
* Prints the value of all configured GPIOs and their directions.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return nothing, print the data directly so we can malloc/free
*/
size_t gpio_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
uint32_t gpio_values;
char *gpio_states_msg = pvPortMalloc(80 + 20 * GPIO_COUNT);
char direction[4];
strcpy(gpio_states_msg,
USH_SHELL_FONT_STYLE_BOLD
USH_SHELL_FONT_COLOR_BLUE
"GPIO_ID\t\tDirection\tValue\r\n"
"-------------------------------------\r\n"
USH_SHELL_FONT_STYLE_RESET);
for (int gpio_num = 0; gpio_num < GPIO_COUNT; gpio_num++) {
if (gpio_settings.gpio_direction[gpio_num] == GPIO_IN) {
strcpy(direction, "IN");
}
else {
strcpy(direction, "OUT");
}
sprintf(gpio_states_msg + strlen(gpio_states_msg),
"GPIO_%d\t\t%s\t\t%d\r\n",
gpio_num,
direction,
gpio_read_single(gpio_num)
);
}
// print directly from this function rather than returning pointer to uShell.
// this allows us to malloc/free rather than using static memory
shell_print(gpio_states_msg);
vPortFree(gpio_states_msg);
// return null since we already printed output
return 0;
}
/**
* @brief '/dev/i2c0' executable callback function.
*
* Interact with I2C bus 0 by reading/writing raw bytes targetted to a specific
* client device address.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void i2c0_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
bool syntax_err = false;
if (argc == 4 &&
(strcmp(argv[1], "read") == 0 || strcmp(argv[1], "write") == 0) &&
(argv[2][0] == '0' && argv[2][1] == 'x')) {
// get i2c target address
uint8_t addr = strtol(&argv[2][2], NULL, 16);
// i2c read
if (strcmp(argv[1], "read") == 0) {
size_t nbytes = strtol(argv[3], NULL, 10);
uint8_t rxdata[nbytes];
// read data from i2c bus
if (i2c0_read(addr, rxdata, nbytes) > 0) {
char *rx_msg_prefix = "Received: 0x";
// allocate heap memory for printable rx data
char *rx_msg = pvPortMalloc(nbytes * 3 + strlen(rx_msg_prefix));
sprintf(rx_msg, rx_msg_prefix);
for (int rx_byte = 0; rx_byte < nbytes; rx_byte++) {
sprintf(rx_msg + strlen(rx_msg_prefix) + (rx_byte * 3), "%02x ", rxdata[rx_byte]);
}
shell_print(rx_msg);
vPortFree(rx_msg);
}
else {
shell_print("No response");
}
}
// i2c write
if (strcmp(argv[1], "write") == 0) {
// guess the length of the data to write before actually checking
size_t nbytes = (strlen(argv[3]) - 2) / 2;
uint8_t txdata[nbytes];
// convert string to data and get real number of bytes
nbytes = hex_string_to_byte_array(argv[3], txdata);
// write data on i2c bus if formatted correctly
if (nbytes > 0) {
int bytes_written = i2c0_write(addr, txdata, nbytes);
if(bytes_written > 0) {
char *tx_msg = pvPortMalloc(16);
sprintf(tx_msg, "Wrote %d bytes", bytes_written);
shell_print(tx_msg);
vPortFree(tx_msg);
}
else {
shell_print("Error writing to bus");
}
}
else {syntax_err = true;}
}
}
else {syntax_err = true;}
if (syntax_err) {
shell_print("command syntax error, see 'help '");
}
}
/**
* @brief '/dev/i2c0' get data callback function.
*
* Sweep through all 7-bit I2C addresses, to see if any client devices are present
* on the I2C bus. Print out a table that looks like this:
*
* I2C Bus Scan
* 0 1 2 3 4 5 6 7 8 9 A B C D E F
* 0
* 1 @
* 2
* 3 @
* 4
* 5
* 6
* 7
*
* E.g. if peripheral device addresses 0x12 and 0x34 were acknowledged.
* Functional idea borrowed from:
* https://github.com/raspberrypi/pico-examples/blob/master/i2c/bus_scan/bus_scan.c
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return nothing, print the data directly so we can malloc/free
*/
size_t i2c0_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
// use malloc rather than passing a pointer to a static char back to uShell,
// since it is a rather large array
char *i2c0_scan_msg = pvPortMalloc(400);
sprintf(i2c0_scan_msg, USH_SHELL_FONT_STYLE_BOLD
USH_SHELL_FONT_COLOR_BLUE
"I2C0 Bus Scan\r\n"
USH_SHELL_FONT_STYLE_RESET
" 0 1 2 3 4 5 6 7 8 9 A B C D E F\r\n");
for (int addr = 0; addr < (1 << 7); ++addr) {
if (addr % 16 == 0) {
sprintf(i2c0_scan_msg + strlen(i2c0_scan_msg), "%02x ", addr);
}
// Perform a 1-byte dummy read from the probe address. If a peripheral device
// acknowledges this address, the function returns the number of bytes
// transferred. If the address byte is ignored, the function returns
// -1.
int ret;
uint8_t rxdata;
if ((addr & 0x78) == 0 || (addr & 0x78) == 0x78) // skip over i2c "reserved" 0000xxx or 1111xxx addresses
ret = -1;
else
ret = i2c0_read(addr, &rxdata, 1);
sprintf(i2c0_scan_msg + strlen(i2c0_scan_msg), ret < 0 ? "." : "@");
sprintf(i2c0_scan_msg + strlen(i2c0_scan_msg), addr % 16 == 15 ? "\r\n" : " ");
}
// print directly from this function rather than returning pointer to uShell.
// this allows us to malloc/free rather than using static memory
shell_print(i2c0_scan_msg);
vPortFree(i2c0_scan_msg);
// return null since we already printed output
return 0;
}
/**
* @brief '/dev/spi0' executable callback function.
*
* Interact with SPI bus 0 by reading/writing to a specific register address.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void spi0_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
bool syntax_err = false;
if (argc == 4 &&
(strcmp(argv[1], "read") == 0 || strcmp(argv[1], "write") == 0) &&
(argv[2][0] == '0' && argv[2][1] == 'x')) {
// get spi target register address
uint8_t addr = strtol(&argv[2][2], NULL, 16);
// spi read
if (strcmp(argv[1], "read") == 0) {
size_t nbytes = strtol(argv[3], NULL, 10);
uint8_t rxdata[nbytes];
// read data from spi bus
if (spi0_read_registers(SPI0_TARGET_DEV_0_CS, addr, rxdata, nbytes) > 0) {
char *rx_msg_prefix = "Received: 0x";
// allocate heap memory for printable rx data
char *rx_msg = pvPortMalloc(nbytes * 3 + strlen(rx_msg_prefix));
sprintf(rx_msg, rx_msg_prefix);
for (int rx_byte = 0; rx_byte < nbytes; rx_byte++) {
sprintf(rx_msg + strlen(rx_msg_prefix) + (rx_byte * 3), "%02x ", rxdata[rx_byte]);
}
shell_print(rx_msg);
vPortFree(rx_msg);
}
else {
shell_print("No response");
}
}
// spi write
if (strcmp(argv[1], "write") == 0) {
// guess the length of the data to write before actually checking
size_t nbytes = (strlen(argv[3]) - 2) / 2;
uint8_t txdata[nbytes];
// convert string to data and get real number of bytes
nbytes = hex_string_to_byte_array(argv[3], txdata);
// write data on i2c bus if formatted correctly
if (nbytes == 1) { // for now we are only expecting to write a single byte to a single register addr
int bytes_written = spi0_write_register(SPI0_TARGET_DEV_0_CS, addr, txdata[0]);
if(bytes_written > 0) {
char *tx_msg = pvPortMalloc(16);
sprintf(tx_msg, "Wrote %d bytes", bytes_written);
shell_print(tx_msg);
vPortFree(tx_msg);
}
else {
shell_print("Error writing to bus");
}
}
else {syntax_err = true;}
}
}
else {syntax_err = true;}
if (syntax_err) {
shell_print("command syntax error, see 'help '");
}
}
/**
* @brief '/dev/spi0' get data callback function.
*
* Interrogates any devices on SPI bus 0 for its ID number.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return nothing, print the data directly so we can malloc/free
*/
size_t spi0_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
// read out the device ID of expected target on the bus
// device ID specified in hardware_config.h
// if more than one device is attached to the bus, this can be made into a loop through chip selects
uint8_t device_id;
int bytes_read = spi0_read_registers(SPI0_TARGET_DEV_0_CS, SPI0_TARGET_DEV_0_ID_REG, &device_id, 1);
char *device_id_msg = pvPortMalloc(30);
if (bytes_read == 1 && device_id != 0) { // assuming device ID is never 0, that would be weird
sprintf(device_id_msg, "found device id: 0x%x\r\n", device_id);
}
else {
sprintf(device_id_msg, "no response on SPI\r\n");
}
// print directly from this function rather than returning pointer to uShell.
// this allows us to malloc/free rather than using static memory
shell_print(device_id_msg);
vPortFree(device_id_msg);
// return null since we already printed output
return 0;
}
/**
* @brief '/dev/adc' get data callback function.
*
* Reads the value of the ADC peripheral and prints the value converted to a
* voltage. See the settings in hardware_config.h for which channel/pin this
* corresponds to.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return length of pointer to the ADC value
*/
size_t adc0_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
static char adc_val[9] = {'\0'};
sprintf(adc_val, "%.3fV\r\n", read_adc(0));
// set pointer to data
*data = (uint8_t*)adc_val;
// return data size
return strlen(adc_val);
}
/**
* @brief '/dev/usb0' get data callback function.
*
* Reads any bytes that are available in the USB device RX buffer, and attempts
* to print them as a string. This of course assumes the other end is sending a
* proper string.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return nothing, print the data directly so we can malloc/free
*/
size_t usb0_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
uint8_t *usb_rx_data = pvPortMalloc(CFG_TUD_CDC_RX_BUFSIZE);
usb_rx_data[0] = '\0'; // make sure initial strlen is zero
while (usb_data_get(usb_rx_data + strlen(usb_rx_data))) {
// try to get all data from usb rx queue
}
// print directly from this function rather than returning pointer to uShell.
// this allows us to malloc/free rather than using static memory
shell_print(usb_rx_data);
vPortFree(usb_rx_data);
// return null since we already printed output
return 0;
}
/**
* @brief '/dev/usb0' set data callback function.
*
* Puts bytes (string) into the USB device TX buffer to send to the host endpoint.
*
* @param ush_file_data_setter Params given by typedef ush_file_data_setter. see ush_types.h
*
* @return nothing
*/
void usb0_set_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t *data, size_t size)
{
usb_data_put(data);
}
/**
* @brief '/dev/uart1' get data callback function.
*
* Reads any bytes that are available in the auxilliary UART RX FIFO, and attempts
* to print them as a string.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return length of pointer to the UART data byte array
*/
size_t uart1_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
static uint8_t uart_rx_data[UART_RX_FIFO_SIZE_AUX];
uart_rx_data[0] = '\0'; // make sure initial strlen is zero
// set pointer to data
*data = (uint8_t*)uart_rx_data;
if (aux_uart_read(uart_rx_data, UART_RX_FIFO_SIZE_AUX) > 0) {
// return data size
return strlen(uart_rx_data);
}
else {
return 0;
}
}
/**
* @brief '/dev/uart1' set data callback function.
*
* Puts bytes (string) into the UART TX FIFO.
*
* @param ush_file_data_setter Params given by typedef ush_file_data_setter. see ush_types.h
*
* @return nothing
*/
void uart1_set_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t *data, size_t size)
{
aux_uart_write(data, size);
}
// dev directory files descriptor
static const struct ush_file_descriptor dev_files[] = {
#if HW_USE_ONBOARD_LED
{
.name = "led", // file name (required)
.description = "onboard LED", // optional file description
.help = NULL, // optional help manual
.exec = NULL, // optional execute callback
.get_data = led_get_data_callback, // optional get data (cat) callback
.set_data = led_set_data_callback // optional set data (echo) callback
},
#endif /* HW_USE_ONBOARD_LED */
{
.name = "time",
.description = "system timer",
.help = NULL,
.exec = NULL,
.get_data = time_get_data_callback,
.set_data = NULL
},
#if HW_USE_GPIO
{
.name = "gpio",
.description = "GPIO pins",
.help = "usage: gpio <\e[3mGPIO num\e[0m>\r\n"
" <\e[3mGPIO num\e[0m> <\e[3mvalue\e[0m>\r\n"
"\r\n"
" cat gpio - print all GPIO states\r\n",
.exec = gpio_exec_callback,
.get_data = gpio_get_data_callback,
.set_data = NULL
},
#endif /* HW_USE_GPIO */
#if HW_USE_I2C0
{
.name = "i2c0",
.description = "I2C bus 0",
.help = "usage: i2c0 <\e[3maddress(0x...)\e[0m> <\e[3mnbytes\e[0m>\r\n"
" <\e[3maddress(0x...)\e[0m> <\e[3mdata(0x...)\e[0m>\r\n"
"\r\n"
" cat i2c0 - scan i2c0 bus and print a table of responding addresses\r\n",
.exec = i2c0_exec_callback,
.get_data = i2c0_get_data_callback,
.set_data = NULL
},
#endif /* HW_USE_I2C0 */
#if HW_USE_SPI0
{
.name = "spi0",
.description = "SPI bus 0",
.help = "usage: spi0 <\e[3mreg addr(0x...)\e[0m> <\e[3mnbytes\e[0m>\r\n"
" <\e[3mreg addr(0x...)\e[0m> <\e[3mdata byte(0x...)\e[0m>\r\n"
"\r\n"
" cat spi0 - read device IDs of all devices in chip select table\r\n",
.exec = spi0_exec_callback,
.get_data = spi0_get_data_callback,
.set_data = NULL
},
#endif /* HW_USE_SPI0 */
#if HW_USE_ADC && ADC0_INIT
{
.name = "adc0",
.description = "Analog-to-Digital Converter",
.help = NULL,
.exec = NULL,
.get_data = adc0_get_data_callback,
.set_data = NULL
},
#endif /* HW_USE_ADC && ADC0_INIT */
#if HW_USE_USB && !CLI_USE_USB
{
.name = "usb0",
.description = "USB data interface",
.help = NULL,
.exec = NULL,
.get_data = usb0_get_data_callback,
.set_data = usb0_set_data_callback
},
#endif /* HW_USE_USB && !CLI_USE_USB */
#if HW_USE_AUX_UART
{
.name = "uart1",
.description = "auxilliary UART",
.help = NULL,
.exec = NULL,
.get_data = uart1_get_data_callback,
.set_data = uart1_set_data_callback
}
#endif /* HW_USE_AUX_UART */
};
// dev directory handler
static struct ush_node_object dev;
void shell_dev_mount(void)
{
// mount dev directory
ush_node_mount(&ush, "/dev", &dev, dev_files, sizeof(dev_files) / sizeof(dev_files[0]));
}
================================================
FILE: cli/node_etc.c
================================================
/******************************************************************************
* @file node_etc.c
*
* @brief /etc folder for the CLI, contains various system configurations
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include
#include "shell.h"
#include "motd.h"
#include "FreeRTOS.h"
/**
* @brief '/etc/motd' get data callback function.
*
* Prints out the Message of the Day (MOTD) character array that is stored in
* motd.h.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return nothing, print the data directly so we can malloc/free
*/
size_t motd_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
// copy motd string into ram
char *motd_msg = pvPortMalloc(strlen(motd_ascii));
strcpy(motd_msg, motd_ascii);
// print to cli
shell_print(motd_msg);
vPortFree(motd_msg);
// return null since we already printed output
return 0;
}
// etc directory files descriptor
static const struct ush_file_descriptor etc_files[] = {
{
.name = "motd", // file name (required)
.description = "message of the day", // optional file description
.help = NULL, // optional help manual
.exec = NULL, // optional execute callback
.get_data = motd_get_data_callback, // optional get data (cat) callback
.set_data = NULL // optional set data (echo) callback
}
};
// etc directory handler
static struct ush_node_object etc;
void shell_etc_mount(void)
{
// mount the /mnt directory
ush_node_mount(&ush, "/etc", &etc, etc_files, sizeof(etc_files) / sizeof(etc_files[0]));
}
================================================
FILE: cli/node_lib.c
================================================
/******************************************************************************
* @file node_lib.c
*
* @brief /lib folder for the CLI, contains hardware peripheral driver interfaces
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include
#include
#include
#include
#include "hardware_config.h"
#include "device_drivers.h"
#include "shell.h"
/**
* @brief '/lib/bme280' get data callback function.
*
* Print readings from Bosch BME280 environmental sensor peripheral.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return length of pointer to the sensor readings string
*/
size_t bme280_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
bme280_sensor_data_t sensor_data;
static char bme280_data_msg[60];
// read compensation data from device and then get sensor readings
if(bme280_read_sensors(&bme280_compensation_params_glob, &sensor_data)) {
sprintf(bme280_data_msg,
"Temp:\t %.1f degC\r\n"
"Hum:\t %.1f%%\r\n"
"Pres:\t %.1f hPa\r\n",
sensor_data.temperature,
sensor_data.humidity,
sensor_data.pressure);
}
else {
sprintf(bme280_data_msg, "error reading sensor\r\n");
}
// set pointer to data
*data = (uint8_t*)bme280_data_msg;
// return data size
return strlen(bme280_data_msg);
}
/**
* @brief '/lib/mcp4725' get data callback function.
*
* Prints the current MCP4725 DAC setting converted to a voltage.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return length of pointer to the voltage setting string
*/
size_t mcp4725_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
static char mcp4725_data_msg[30];
float voltage;
voltage = mcp4725_get_voltage();
if (voltage >= 0) {
sprintf(mcp4725_data_msg, "%.2fV\r\n", voltage);
}
else {
strcpy(mcp4725_data_msg, "error reading DAC value\r\n");
}
// set pointer to data
*data = (uint8_t*)mcp4725_data_msg;
// return data size
return strlen(mcp4725_data_msg);
}
/**
* @brief '/lib/mcp4725' set data callback function.
*
* Sets the DAC output register given a user-defined voltage.
*
* @param ush_file_data_setter Params given by typedef ush_file_data_setter. see ush_types.h
*
* @return nothing
*/
void mcp4725_set_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t *data, size_t size)
{
float voltage_setting = strtof(data, NULL);
if (voltage_setting >= 0 && voltage_setting <= MCP4725_VDD) {
mcp4725_set_voltage(voltage_setting, false);
}
}
// lib directory files descriptor
static const struct ush_file_descriptor lib_files[] = {
#if HW_USE_SPI0 && BME280_ATTACHED
{
.name = "bme280", // file name (required)
.description = "Bosch BME280 environmental sensor", // optional file description
.help = NULL, // optional help manual
.exec = NULL, // optional execute callback
.get_data = bme280_get_data_callback, // optional get data (cat) callback
.set_data = NULL // optional set data (echo) callback
},
#endif
#if HW_USE_I2C0 && MCP4725_ATTACHED
{
.name = "mcp4725",
.description = "Microchip MCP4725 digital-to-analog converter",
.help = "usage: cat mcp4725 - get current voltage setting of DAC\r\n"
" echo \e[3mx.xxx\e[0m > mcp4725 - set DAC voltage\r\n",
.exec = NULL,
.get_data = mcp4725_get_data_callback,
.set_data = mcp4725_set_data_callback
}
#endif
};
// lib directory handler
static struct ush_node_object lib;
void shell_lib_mount(void)
{
// mount dev directory
ush_node_mount(&ush, "/lib", &lib, lib_files, sizeof(lib_files) / sizeof(lib_files[0]));
}
================================================
FILE: cli/node_mnt.c
================================================
/******************************************************************************
* @file node_mnt.c
*
* @brief /mnt folder for the CLI, contains littlefs filesystem(s)
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include
#include
#include "shell.h"
#include "services.h"
#include "service_queues.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
/**
* @brief '/mnt/flash0' executable callback function.
*
* Interact with the onboard flash0 filesystem (list directories, read/write
* files, etc.), by putting the action request into the storagemanager's queue
* for servicing, and then reading out any resulting data from the global
* storagemanager data structure.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void flash0_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
bool syntax_err = false;
struct storman_item_t smi;
// make sure storagemanager is actually running
if (xTaskGetHandle(xstr(SERVICE_NAME_STORMAN)) == NULL) {
shell_print("error, " xstr(SERVICE_NAME_STORMAN) " service is not running");
}
else if (argc > 1) {
if (strcmp(argv[1], "lsdir") == 0 && argc == 3) {
smi.action = LSDIR;
strcpy(smi.sm_item_name, argv[2]);
storman_request(&smi);
// wait for storagemanager to provide semaphore indicating data is ready
if (xSemaphoreTake(smi_glob_sem, DELAY_STORMAN * 2) == pdTRUE) {
shell_print(smi_glob.sm_item_data);
}
}
else if (strcmp(argv[1], "mkdir") == 0 && argc == 3) {
smi.action = MKDIR;
strcpy(smi.sm_item_name, argv[2]);
storman_request(&smi);
}
else if (strcmp(argv[1], "rmdir") == 0 && argc == 3) {
smi.action = RMDIR;
strcpy(smi.sm_item_name, argv[2]);
storman_request(&smi);
}
else if (strcmp(argv[1], "mkfile") == 0 && argc == 3) {
smi.action = MKFILE;
strcpy(smi.sm_item_name, argv[2]);
storman_request(&smi);
}
else if (strcmp(argv[1], "rmfile") == 0 && argc == 3) {
smi.action = RMFILE;
strcpy(smi.sm_item_name, argv[2]);
storman_request(&smi);
}
else if (strcmp(argv[1], "dumpfile") == 0 && argc == 3) {
smi.action = DUMPFILE;
strcpy(smi.sm_item_name, argv[2]);
storman_request(&smi);
// wait for storagemanager to provide semaphore indicating data is ready
if (xSemaphoreTake(smi_glob_sem, DELAY_STORMAN * 2) == pdTRUE) {
shell_print(smi_glob.sm_item_data);
}
}
else if (strcmp(argv[1], "readfile") == 0 && argc == 5) {
smi.action = READFILE;
strcpy(smi.sm_item_name, argv[2]);
smi.sm_item_offset = (lfs_soff_t)strtol(argv[3], NULL, 10);
smi.sm_item_size = strtol(argv[4], NULL, 10);
storman_request(&smi);
// wait for storagemanager to provide semaphore indicating data is ready
if (xSemaphoreTake(smi_glob_sem, DELAY_STORMAN * 2) == pdTRUE) {
shell_print(smi_glob.sm_item_data);
}
}
else if (strcmp(argv[1], "writefile") == 0 && argc == 4) {
smi.action = WRITEFILE;
strcpy(smi.sm_item_name, argv[2]);
strcpy(smi.sm_item_data, argv[3]);
storman_request(&smi);
}
else if (strcmp(argv[1], "appendfile") == 0 && argc == 4) {
smi.action = APPENDFILE;
strcpy(smi.sm_item_name, argv[2]);
strcpy(smi.sm_item_data, argv[3]);
storman_request(&smi);
}
else if (strcmp(argv[1], "filestat") == 0 && argc == 3) {
smi.action = FILESTAT;
strcpy(smi.sm_item_name, argv[2]);
storman_request(&smi);
// wait for storagemanager to provide semaphore indicating data is ready
if (xSemaphoreTake(smi_glob_sem, DELAY_STORMAN * 2) == pdTRUE) {
shell_print(smi_glob.sm_item_data);
}
}
else if (strcmp(argv[1], "fsstat") == 0 && argc == 2) {
smi.action = FSSTAT;
storman_request(&smi);
// wait for storagemanager to provide semaphore indicating data is ready
if (xSemaphoreTake(smi_glob_sem, DELAY_STORMAN * 2) == pdTRUE) {
shell_print(smi_glob.sm_item_data);
}
}
else if (strcmp(argv[1], "format") == 0 && argc == 2) {
smi.action = FORMAT;
storman_request(&smi);
// wait for storagemanager to provide semaphore indicating data is ready
if (xSemaphoreTake(smi_glob_sem, DELAY_STORMAN * 2) == pdTRUE) {
shell_print(smi_glob.sm_item_data);
}
}
else if (strcmp(argv[1], "unmount") == 0 && argc == 2) {
smi.action = UNMOUNT;
storman_request(&smi);
shell_print("/mnt folder unmounted, restart storagemanager service to re-mount");
}
else {syntax_err = true;}
}
else {syntax_err = true;}
if (syntax_err) {
shell_print("command syntax error, see 'help '");
}
}
// mnt directory files descriptor
static const struct ush_file_descriptor mnt_files[] = {
{
.name = "flash0",
.description = "onboard flash filesystem",
.help = "usage: flash0 <\e[3mname\e[0m>,\r\n"
" <\e[3mname\e[0m> <\e[3moffset\e[0m> <\e[3mlength\e[0m>,\r\n"
" <\e[3mname\e[0m> <\e[3mdata\e[0m>,\r\n"
" <\e[3mname\e[0m>,\r\n"
" \r\n"
" \r\n"
" \r\n",
.exec = flash0_exec_callback,
.get_data = NULL,
.set_data = NULL
}
};
// mnt directory handler
static struct ush_node_object mnt;
void shell_mnt_mount(void)
{
// mount the /mnt directory
ush_node_mount(&ush, "/mnt", &mnt, mnt_files, sizeof(mnt_files) / sizeof(mnt_files[0]));
}
void shell_mnt_unmount(void)
{
// unmount the /mnt directory
ush_node_unmount(&ush, "/mnt");
}
================================================
FILE: cli/node_net.c
================================================
/******************************************************************************
* @file node_net.c
*
* @brief /net folder for the CLI, contains network tools and network hardware
* interaction functions
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 01-24-2025
*
* @copyright Copyright (c) 2025 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include
#include
#include
#include
#include "hardware_config.h"
#include "shell.h"
#include "cli_utils.h"
#include "services.h"
#include "service_queues.h"
#include "lfs.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
static void wifi_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
netman_action_t nma;
if (argc == 4) {
if (strcmp(argv[1], "setauth") == 0) { // set the wifi network authentication parameters
// create wifi_auth file with new credentials (or overwrite existing)
struct storman_item_t smi;
smi.action = WRITEFILE;
strcpy(smi.sm_item_name, "wifi_auth");
sprintf(smi.sm_item_data, "%s,%s", argv[2], argv[3]);
storman_request(&smi);
shell_print("wifi network credentials set");
}
else {
shell_print("command syntax error, see 'help '");
}
}
else if (argc == 2) {
if (strcmp(argv[1], "connect") == 0) { // connect to the wifi network defined by 'setauth'
nma = NETJOIN;
netman_request(nma);
}
else if (strcmp(argv[1], "disconnect") == 0) { // disconnect from the wifi network
nma = NETLEAVE;
netman_request(nma);
}
else {
shell_print("command syntax error, see 'help '");
}
}
else {
shell_print("command syntax error, see 'help '");
}
}
// net directory files descriptor
static const struct ush_file_descriptor net_files[] = {
#if HW_USE_WIFI
{
.name = "wifi", // file name (required)
.description = "WiFi network interface", // optional file description
.help = "usage: wifi \r\n" // optional help manual
" wifi setauth <\e[3mssid\e[0m>" // optional help manual
" <\e[3mpassword\e[0m>\r\n",
.exec = wifi_exec_callback, // optional execute callback
.get_data = NULL, // optional get data (cat) callback
.set_data = NULL // optional set data (echo) callback
},
#endif /* HW_USE_WIFI */
};
// net directory handler
static struct ush_node_object net;
void shell_net_mount(void)
{
// mount net directory
ush_node_mount(&ush, "/net", &net, net_files, sizeof(net_files) / sizeof(net_files[0]));
}
================================================
FILE: cli/node_proc.c
================================================
/******************************************************************************
* @file node_proc.c
*
* @brief /proc folder for the CLI, contains system information
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include
#include
#include
#include
#include "hardware_config.h"
#include "shell.h"
#include "FreeRTOS.h"
#include "task.h"
#include "version.h"
/**
* @brief '/proc/mcuinfo' get data callback function.
*
* Returns infomation about the MCU by using the built-in 'cat' CLI command.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return nothing, print the data directly so we can malloc/free
*/
size_t mcuinfo_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
// use malloc rather than passing a pointer to a static char back to uShell,
// since it is a rather large array
char *mcuinfo_msg = pvPortMalloc(250);
const char* scheduler_state[] = {"suspended", "not started", "running"};
sprintf(mcuinfo_msg, "MCU: " xstr(MCU_NAME) ", running at %ld Hz\r\n", get_sys_clk_hz());
sprintf(mcuinfo_msg + strlen(mcuinfo_msg), "Chip version: %d\r\n", get_chip_version()); // MCU silicon version
sprintf(mcuinfo_msg + strlen(mcuinfo_msg), "ROM version: %d\r\n", get_rom_version()); // MCU chip ROM version
sprintf(mcuinfo_msg + strlen(mcuinfo_msg), "Board: %s\r\n", xstr(BOARD));
sprintf(mcuinfo_msg + strlen(mcuinfo_msg),
"RTOS scheduler is [ %s ], %ld tasks registered\r\n",
scheduler_state[(int32_t)xTaskGetSchedulerState()], uxTaskGetNumberOfTasks());
sprintf(mcuinfo_msg + strlen(mcuinfo_msg), xstr(PROJECT_NAME) " CLI running on core: %d\r\n", get_core());
// print directly from this function rather than returning pointer to uShell.
// this allows us to malloc/free rather than using static memory
shell_print(mcuinfo_msg);
vPortFree(mcuinfo_msg);
// return null since we already printed output
return 0;
}
/**
* @brief '/proc/version' get data callback function.
*
* Returns version information by using the built-in 'cat' CLI command.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return nothing, print the data directly so we can malloc/free
*/
size_t version_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
// use malloc rather than passing a pointer to a static char array back to uShell,
// since it is a rather large array
char *version_msg = pvPortMalloc(400);
sprintf(version_msg, "" USH_SHELL_FONT_STYLE_BOLD USH_SHELL_FONT_COLOR_BLUE);
sprintf(version_msg + strlen(version_msg), xstr(PROJECT_NAME) " version:\t" xstr(PROJECT_VERSION) "\r\n"); // Top level project version
sprintf(version_msg + strlen(version_msg), "" USH_SHELL_FONT_STYLE_RESET);
sprintf(version_msg + strlen(version_msg), "Build branch:\t\t%s\r\n", git_Branch()); // Git commit date of build
sprintf(version_msg + strlen(version_msg), "Git commit date:\t%s\r\n", git_CommitDate()); // Git commit date of build
sprintf(version_msg + strlen(version_msg), "Git commit hash:\t%s\r\n", git_CommitSHA1()); // Git commit hash of build
sprintf(version_msg + strlen(version_msg), "%s version:\t%d.%d%c\r\n", BBOS_NAME, // BreadboardOS version
BBOS_VERSION_MAJOR,
BBOS_VERSION_MINOR,
BBOS_VERSION_MOD);
sprintf(version_msg + strlen(version_msg), "FreeRTOS version:\t%s\r\n", tskKERNEL_VERSION_NUMBER); // FreeRTOS Kernel version
sprintf(version_msg + strlen(version_msg), "%s version:\t%s\r\n", USH_NAME, USH_VERSION); // microshell version
sprintf(version_msg + strlen(version_msg), "littlefs version:\t%d.%d\r\n", LFS_VERSION_MAJOR, // littlefs version
LFS_VERSION_MINOR);
// print directly from this function rather than returning pointer to uShell.
// this allows us to malloc/free rather than using static memory
shell_print(version_msg);
vPortFree(version_msg);
// return null since we already printed output
return 0;
}
/**
* @brief '/proc/resetreason' get data callback function.
*
* Returns the reason for the last reset by using the built-in 'cat' CLI command.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return length of pointer to the reset reason string
*/
size_t resetreason_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
// get reset reason string using global reset reason type captured at boot
char *reset_reason_string = get_reset_reason_string(last_reset_reason);
// copy the pointer to the reset reason string
*data = (uint8_t*)reset_reason_string;
// return data size
return strlen(reset_reason_string);
}
/**
* @brief '/proc/uptime' get data callback function.
*
* Print the system uptime (days, hours, minutes, seconds) since last system boot.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return nothing, print the data directly so we can malloc/free
*/
size_t uptime_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
// get current microsecond timer value
uint64_t current_time_us = get_time_us();
// calculate time divisions, i.e. maths
uint16_t num_seconds = (uint16_t) (current_time_us / 1E6) % 60;
uint16_t num_minutes = (uint16_t) (((current_time_us / 1E6) - num_seconds) / 60) % 60;
uint16_t num_hours = (uint16_t) (((((current_time_us / 1E6) - num_seconds) / 60) - num_minutes) / 60) % 24;
uint16_t num_days = (uint16_t) ((((((current_time_us / 1E6) - num_seconds) / 60) - num_minutes) / 60) - num_hours) / 24;
// build formatted string
char *uptime_msg = pvPortMalloc(55);
snprintf(uptime_msg, 55, "System up %d days, %d hours, %d minutes, %d seconds\r\n", num_days, num_hours, num_minutes, num_seconds);
// print the uptime msg
shell_print(uptime_msg);
vPortFree(uptime_msg);
// return null since we printed directly to the shell
return 0;
}
// proc directory files descriptor
static const struct ush_file_descriptor proc_files[] = {
{
.name = "mcuinfo", // file name (required)
.description = "get details about the MCU", // optional file description
.help = NULL, // optional help manual
.exec = NULL, // optional execute callback
.get_data = mcuinfo_get_data_callback, // optional get data (cat) callback
.set_data = NULL // optional set data (echo) callback
},
{
.name = "version",
.description = "get version numbers of firmware libraries",
.help = NULL,
.exec = NULL,
.get_data = version_get_data_callback,
.set_data = NULL
},
{
.name = "resetreason",
.description = "get the last reset reason",
.help = NULL,
.exec = NULL,
.get_data = resetreason_get_data_callback,
.set_data = NULL
},
{
.name = "uptime",
.description = "get system uptime since boot",
.help = NULL,
.exec = NULL,
.get_data = uptime_get_data_callback,
.set_data = NULL
}
};
// proc directory handler
static struct ush_node_object proc;
void shell_proc_mount(void)
{
// mount proc directory
ush_node_mount(&ush, "/proc", &proc, proc_files, sizeof(proc_files) / sizeof(proc_files[0]));
}
================================================
FILE: cli/node_root.c
================================================
/******************************************************************************
* @file node_root.c
*
* @brief root folder ('/') for the CLI containing directories and files
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include
#include "hardware_config.h"
/**
* @brief 'info.txt' get data callback function.
*
* Returns the "contents" of the info.txt file by using the built-in 'cat' CLI command.
*
* @param ush_file_data_getter Params given by typedef ush_file_data_getter. see ush_types.h
*
* @return length of pointer to the text strings to print
*/
size_t info_get_data_callback(struct ush_object *self, struct ush_file_descriptor const *file, uint8_t **data)
{
static char *info = xstr(PROJECT_NAME) ", compiled with love\r\n"; // the contents of the file
// set pointer to data
*data = (uint8_t*)info;
// return data size
return strlen(info);
}
// root directory files descriptor
static const struct ush_file_descriptor root_files[] = {
{
.name = "info.txt", // file name (required)
.description = NULL, // optional file description
.help = NULL, // optional help manual
.exec = NULL, // optional execute callback
.get_data = info_get_data_callback, // optional get data (cat) callback
.set_data = NULL // optional set data (echo) callback
}
};
// root directory handler
static struct ush_node_object root;
void shell_root_mount(void)
{
// mount root directory
ush_node_mount(&ush, "/", &root, root_files, sizeof(root_files) / sizeof(root_files[0]));
}
================================================
FILE: cli/shell.c
================================================
/******************************************************************************
* @file shell.h
*
* @brief Microshell instance definition, settings, & wrapper functions to use
* at CLI application level
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include
#include
#include "hardware_config.h"
#include "shell.h"
#include "services.h"
#include "service_queues.h"
#include "FreeRTOS.h"
#include "queue.h"
#include "rtos_utils.h"
// microshell character read interface
static int ush_read(struct ush_object *self, char *ch)
{
char inchar = NOCHAR;
if (CLI_USE_USB) {
inchar = cli_usb_getc(); // CLI over USB get single char
}
else {
inchar = cli_uart_getc(); // CLI over UART get single char
}
if(inchar != NOCHAR) {
*ch = inchar;
return 1;
}
return 0;
}
// microshell character write interface
static int ush_write(struct ush_object *self, char ch)
{
if (CLI_USE_USB) {
return cli_usb_putc(ch); // CLI over USB put single char
}
else {
return cli_uart_putc(ch); // CLI over UART put single char
}
}
// I/O interface descriptor
static const struct ush_io_interface ush_iface = {
.read = ush_read,
.write = ush_write,
};
static char ush_in_buf[BUF_IN_SIZE];
static char ush_out_buf[BUF_OUT_SIZE];
static char ush_hist_buf[SHELL_HISTORY_LINES * SHELL_WORK_BUFFER_SIZE];
// global microshell instance handler (extern declared in shell.h)
struct ush_object ush;
// microshell custom prompt formatting structure - defined in shell.h
static const struct ush_prompt_format ush_prompt = {
.prompt_prefix = SHELL_PROMPT_PREFIX,
.prompt_space = SHELL_PROMPT_SPACE,
.prompt_suffix = SHELL_PROMPT_SUFFIX
};
// microshell command history structure - sizes in shell.h
static ush_history ush_cmd_hist = {
.lines = SHELL_HISTORY_LINES,
.length = SHELL_WORK_BUFFER_SIZE,
.buffer = ush_hist_buf
};
// microshell descriptor
static const struct ush_descriptor ush_desc = {
.io = &ush_iface, // I/O interface pointer
.input_history = &ush_cmd_hist, // command history buffer
.input_buffer = ush_in_buf, // working input buffer
.input_buffer_size = sizeof(ush_in_buf), // working input buffer size
.output_buffer = ush_out_buf, // working output buffer
.output_buffer_size = sizeof(ush_out_buf), // working output buffer size
.path_max_length = PATH_MAX_SIZE, // path maximum length (stack)
.hostname = HOST_NAME, // hostname (in prompt) - defined in hardware_config.h
.prompt_format = &ush_prompt // custom prompt formatting
};
// declare the extern functions to build the global directory structure - defined in individual source files
extern void shell_commands_add(void);
extern void shell_root_mount(void);
extern void shell_dev_mount(void);
extern void shell_bin_mount(void);
extern void shell_proc_mount(void);
extern void shell_etc_mount(void);
extern void shell_lib_mount(void);
extern void shell_net_mount(void);
void shell_init(void)
{
// initialize microshell instance
ush_init(&ush, &ush_desc);
// add commands
shell_commands_add();
// mount nodes (root must be first) - order printed with 'ls' follows reverse
// of this mount order - if nodes are mounted on the fly later, they will
// be on the top
shell_root_mount();
shell_lib_mount();
shell_dev_mount();
shell_proc_mount();
shell_bin_mount();
shell_etc_mount();
// note that /mnt is currently mounted by storagemanager service
// note that /net is currently mounted by networkmanager service
}
void shell_service(void)
{
ush_service(&ush);
}
void shell_print(char *buf)
{
// print buffer using microshell interface
ush_print(&ush, buf);
// wait until printing is finished to exit function (blocking)
while(shell_is_printing()) {}
}
void shell_print_slow(const char *buf) {
uint16_t string_index = 0;
char char_buf[2];
bool end_of_string = false;
// move through the print buffer one character at a time
while (!end_of_string) {
char_buf[0] = buf[string_index]; // copy current char into print buffer
char_buf[1] = '\0'; // terminate the single char as a string
if (buf[string_index] == '\n') { // upon newline block printing for specified delay time
string_index++;
task_delay_ms(SLOW_PRINT_LINE_DELAY_MS);
}
else if (buf[string_index] == '\0') { // end of print buffer
end_of_string = true;
}
else {
string_index++;
task_delay_ms(SLOW_PRINT_CHAR_DELAY_MS);
}
if (!end_of_string) {
ush_print(&ush, char_buf);
while(shell_is_printing()) {}
}
}
}
bool shell_is_printing(void)
{
// check if microshell is currently in writing state
if (ush.state == USH_STATE_WRITE_CHAR) {
ush_service(&ush); // keep servicing the shell if it is still writing chars
return true;
} else {
return false;
}
}
char * timestamp(void)
{
static char timestamp_msg[TIMESTAMP_LEN]; // 14 bytes plus spaces and brackets, ~3 years before rollover
snprintf(timestamp_msg, TIMESTAMP_LEN, "[%llu\t] ", get_time_us());
return timestamp_msg;
}
================================================
FILE: cli/shell.h
================================================
/******************************************************************************
* @file shell.h
*
* @brief Microshell custom settings and wrapper functions to use at the CLI
* application layer
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#ifndef SHELL_H
#define SHELL_H
#include
#include
// microshell in/out buffer allocations
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_ARCH_STM32)
#define BUF_IN_SIZE 128
#define BUF_OUT_SIZE 128
#define PATH_MAX_SIZE 128
#define SHELL_HISTORY_LINES 6
#define SHELL_WORK_BUFFER_SIZE 256
#elif defined(PICO)
#define BUF_IN_SIZE 128
#define BUF_OUT_SIZE 1024
#define PATH_MAX_SIZE 128
#define SHELL_HISTORY_LINES 6
#define SHELL_WORK_BUFFER_SIZE 256
#else
#define BUF_IN_SIZE 32
#define BUF_OUT_SIZE 32
#define PATH_MAX_SIZE 32
#define SHELL_HISTORY_LINES 6
#define SHELL_WORK_BUFFER_SIZE 256
#endif
// custom prompt formatting -
// microshell prompt format is prompt_prefix+HOST_NAME+prompt_space+[path]+prompt_suffix
// see "ush_shell.h" for font formatting codes
#ifndef HOST_NAME
#define HOST_NAME "bbos"
#endif
#define SHELL_PROMPT_PREFIX USH_SHELL_FONT_COLOR_MAGENTA \
USH_SHELL_FONT_STYLE_BOLD \
"[" \
USH_SHELL_FONT_COLOR_YELLOW
#define SHELL_PROMPT_SPACE USH_SHELL_FONT_COLOR_CYAN \
" "
#define SHELL_PROMPT_SUFFIX USH_SHELL_FONT_COLOR_MAGENTA \
USH_SHELL_FONT_STYLE_BOLD \
"]$ " \
USH_SHELL_FONT_STYLE_RESET
// miscellaneous definitions
#define NOCHAR 255 // return value of cli_uart_getc() in ush_read() if timeout occurs
#define TIMESTAMP_LEN 20 // length of timestamp() string to use when sizing print buffers
#define SLOW_PRINT_CHAR_DELAY_MS 1 // shell_print_slow() OS tick delay between chars
#define SLOW_PRINT_LINE_DELAY_MS 5 // shell_print_slow() OS tick delay between lines
// global microshell instance handler
extern struct ush_object ush;
/**
* @brief Initialize the CLI shell.
*
* Initializes the microshell instance and mounts all directory and file nodes.
*
* @param none
*
* @return nothing
*/
void shell_init(void);
/**
* @brief Service routine for the CLI shell.
*
* Runs in a loop to service all microshell functions.
*
* @param none
*
* @return nothing
*/
void shell_service(void);
/**
* @brief Print string output in the shell.
*
* Wrapper which allows other functions within the CLI to print output to the shell.
* Anything outside the CLI task should use the print queue (cli_print_raw() or cli_print_timestamped()).
* Note that this function blocks until printing is complete.
*
* @param buf pointer to the string to print
*
* @return nothing
*/
void shell_print(char *buf);
/**
* @brief Print string output in the shell, slowly.
*
* Similar to shell_print(), with the addition of RTOS task delays to give the output
* text that groovy retro console feel. This is a silly function with no real world
* value. Note that this is only intended for large multi-line prints like MOTD,
* which are stored in flash (const char*)
*
* @param buf pointer to the string to print
*
* @return nothing
*/
void shell_print_slow(const char *buf);
/**
* @brief Check if the shell is currently printing.
*
* Wrap this function in while() to wait until the shell is done printing all
* characters of a string before moving on. This is implemented inside shell_print().
*
* @param none
*
* @return true if shell is still printing the current string, false when complete
*/
bool shell_is_printing(void);
/**
* @brief Generate a timestamp that can be used in shell prints.
*
* Provides a pointer to a string in '[ xxx ]' format that is useful for
* displaying clear timestamps along with shell output.
*
* @param none
*
* @return pointer to the timestamp string
*/
char * timestamp(void);
/**
* @brief Shell serial read interface.
*
* Hardware-abstracted, non-blocking read interface for microshell to receive
* input characters.
*
* @param ush_io_interface_read_char Params given by typedef ush_io_interface_read_char. see ush_types.h
*
* @return See ush_io_interface_read_char in ush_types.h
*/
static int ush_read(struct ush_object *self, char *ch);
/**
* @brief Shell serial write interface.
*
* Hardware-abstracted, non-blocking write interface for microshell to send
* output characters.
*
* @param ush_io_interface_write_char Params given by typedef ush_io_interface_write_char. see ush_types.h
*
* @return See ush_io_interface_read_write in ush_types.h
*/
static int ush_write(struct ush_object *self, char ch);
#endif /* SHELL_H */
================================================
FILE: cli/shell_cmd.c
================================================
/******************************************************************************
* @file shell_cmd.c
*
* @brief Defines any additional CLI builtin shell commands
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include
#include "shell.h"
/**
* @brief 'clear' shell command callback function.
*
* Clear the screen - works when using a ANSI-compatible terminal.
*
* @param ush_file_execute_callback Params given by typedef ush_file_execute_callback. see ush_types.h
*
* @return nothing
*/
static void clear_exec_callback(struct ush_object *self, struct ush_file_descriptor const *file, int argc, char *argv[])
{
static char clear_msg[] = "" USH_SHELL_CLEAR_SCREEN
USH_SHELL_HOME;
shell_print(clear_msg);
}
// cmd files descriptor
static const struct ush_file_descriptor cmd_files[] = {
{
.name = "clear",
.description = "clear screen",
.help = NULL,
.exec = clear_exec_callback,
},
};
// cmd commands handler
static struct ush_node_object cmd;
void shell_commands_add(void)
{
// add custom commands
ush_commands_add(&ush, &cmd, cmd_files, sizeof(cmd_files) / sizeof(cmd_files[0]));
}
================================================
FILE: driver_lib/CMakeLists.txt
================================================
# add source files to the top-level project
target_sources(${PROJ_NAME} PRIVATE
device_drivers.c
bme280.c
mcp4725.c
)
================================================
FILE: driver_lib/bme280.c
================================================
/******************************************************************************
* @file bme280.c
*
* @brief Hardware interface driver functions for the Bosch BME280 environmental
* sensor peripheral. Calibration routines based on Bosch datasheet, using
* a ridiculously convoluted confluence of algorithms.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include "device_drivers.h"
#include "hardware_config.h"
// global storage for bme280 compensation params, read out once in bme280_init()
bme280_compensation_params_t bme280_compensation_params_glob;
static int32_t bme280_compensate_temperature(bme280_compensation_params_t *compensation_params, int32_t adc_T) {
int32_t var1, var2, T;
// calculate compensation factors for temperature reading - is there any possible way there isn't a typo here?
var1 = ((((adc_T >> 3) - ((int32_t) compensation_params->temp_comp.T1 << 1))) * ((int32_t) compensation_params->temp_comp.T2)) >> 11;
var2 = (((((adc_T >> 4) - ((int32_t) compensation_params->temp_comp.T1)) * ((adc_T >> 4) - ((int32_t) compensation_params->temp_comp.T1))) >> 12) * ((int32_t) compensation_params->temp_comp.T3)) >> 14;
// save fine temperature for other compensations - temp calc needs to always come before pressure, humidity
compensation_params->temp_fine = var1 + var2;
// calculate calibrated temperature
T = (compensation_params->temp_fine * 5 + 128) >> 8;
return T;
}
static uint32_t bme280_compensate_pressure(bme280_compensation_params_t *compensation_params, int32_t adc_P) {
int64_t var1, var2, P;
// calculate compensation factors for barometric pressure reading - seriously I have never manipulated so many bits
var1 = ((int64_t) compensation_params->temp_fine) - 128000;
var2 = var1 * var1 * (int64_t) compensation_params->press_comp.P6;
var2 = var2 + ((var1 * (int64_t) compensation_params->press_comp.P5) << 17);
var2 = var2 + ((int64_t) compensation_params->press_comp.P4 << 35);
var1 = ((var1 * var1 * (int64_t) compensation_params->press_comp.P3) >> 8) + ((var1 * (int64_t) compensation_params->press_comp.P2) << 12);
var1 = (((((int64_t) 1) << 47) + var1)) * ((int64_t) compensation_params->press_comp.P1) >> 33;
if (var1 == 0) return 0; // avoid possible divide by zero exception by returning early
P = 1048576 - adc_P;
P = (((P << 31) - var2) * 3125) / var1;
// further calculate, the BME280 is a great example of how not to design an ASIC data interface
var1 = (((int64_t) compensation_params->press_comp.P9) * (P >> 13) * (P >> 13)) >> 25;
var2 = (((int64_t) compensation_params->press_comp.P8) * P) >> 19;
// calculate final calibrated barometric pressure
P = ((P + var1 + var2) >> 8) + (((int64_t) compensation_params->press_comp.P7) << 4);
return (uint32_t) P;
}
static uint32_t bme280_compensate_humidity(bme280_compensation_params_t *compensation_params, int32_t adc_H) {
int32_t H;
// here we go again...
H = (compensation_params->temp_fine - ((int32_t) 76800));
H = (((((adc_H << 14) - (((int32_t) compensation_params->hum_comp.H4) << 20) - (((int32_t) compensation_params->hum_comp.H5) * H)) +
((int32_t) 16384)) >> 15) * (((((((H * ((int32_t) compensation_params->hum_comp.H6)) >> 10) * (((H *((int32_t) compensation_params->hum_comp.H3)) >> 11) +
((int32_t) 32768))) >> 10) + ((int32_t) 2097152)) * ((int32_t) compensation_params->hum_comp.H2) + 8192) >> 14));
H = (H - (((((H >> 15) * (H >> 15)) >> 7) * ((int32_t) compensation_params->hum_comp.H1)) >> 4));
H = (H < 0 ? 0 : H);
H = (H > 419430400 ? 419430400 : H);
// I'm as stumped as you are
return (uint32_t) (H >> 12);
}
int bme280_read_compensation_parameters(bme280_compensation_params_t *compensation_params) {
uint8_t comp_params_buffer[26];
int bytes_read;
// read out factory compensation parameter data bytes 0-25 (comp data is split between 2 memory blocks)
bytes_read = spi0_read_registers(SPI0_TARGET_DEV_0_CS, 0x88, comp_params_buffer, 26);
if (bytes_read == 26) {
// break out data into individual compensation factors - following
// sec. 4.2.2 of BME280 data sheet "Trimming parameter readout"
// temperature compensation
compensation_params->temp_comp.T1 = comp_params_buffer[0] | (comp_params_buffer[1] << 8);
compensation_params->temp_comp.T2 = comp_params_buffer[2] | (comp_params_buffer[3] << 8);
compensation_params->temp_comp.T3 = comp_params_buffer[4] | (comp_params_buffer[5] << 8);
// barometric pressure compensation
compensation_params->press_comp.P1 = comp_params_buffer[6] | (comp_params_buffer[7] << 8);
compensation_params->press_comp.P2 = comp_params_buffer[8] | (comp_params_buffer[9] << 8);
compensation_params->press_comp.P3 = comp_params_buffer[10] | (comp_params_buffer[11] << 8);
compensation_params->press_comp.P4 = comp_params_buffer[12] | (comp_params_buffer[13] << 8);
compensation_params->press_comp.P5 = comp_params_buffer[14] | (comp_params_buffer[15] << 8);
compensation_params->press_comp.P6 = comp_params_buffer[16] | (comp_params_buffer[17] << 8);
compensation_params->press_comp.P7 = comp_params_buffer[18] | (comp_params_buffer[19] << 8);
compensation_params->press_comp.P8 = comp_params_buffer[20] | (comp_params_buffer[21] << 8);
compensation_params->press_comp.P9 = comp_params_buffer[22] | (comp_params_buffer[23] << 8);
// humidity compensation
compensation_params->hum_comp.H1 = comp_params_buffer[25];
// read out factory compensation parameter data bytes 26-32, re-using the same buffer
spi0_read_registers(SPI0_TARGET_DEV_0_CS, 0xE1, comp_params_buffer, 7);
compensation_params->hum_comp.H2 = comp_params_buffer[0] | (comp_params_buffer[1] << 8);
compensation_params->hum_comp.H3 = comp_params_buffer[2];
compensation_params->hum_comp.H4 = (comp_params_buffer[3] << 4) | (comp_params_buffer[4] & 0x0F);
compensation_params->hum_comp.H5 = (comp_params_buffer[4] >> 4) | (comp_params_buffer[5] << 4);
compensation_params->hum_comp.H6 = comp_params_buffer[7];
return 1;
}
else return 0;
}
int bme280_init(void) {
int registers_written = 0;
// read out compensation parameters from device ROM and store in global structure
if (bme280_read_compensation_parameters(&bme280_compensation_params_glob)) {
// humidity oversampling register = x1
registers_written += spi0_write_register(SPI0_TARGET_DEV_0_CS, 0xF2, 0x01);
// set other oversampling modes, run mode normal
registers_written += spi0_write_register(SPI0_TARGET_DEV_0_CS, 0xF4, 0x27);
}
// make sure BME280 configuration has taken place
if (registers_written == 2)
return 1;
else return 0;
}
int bme280_read_sensors(bme280_compensation_params_t *compensation_params, bme280_sensor_data_t *sensor_data) {
uint8_t raw_data_buffer[8];
int32_t temp_raw;
uint32_t press_raw, hum_raw;
// read raw data (uncalibrated) from sensors
if (spi0_read_registers(SPI0_TARGET_DEV_0_CS, 0xF7, raw_data_buffer, 8) == 8) {
// shift data into the appropriate bin (temp, press, hum)
temp_raw = ((uint32_t) raw_data_buffer[3] << 12) | ((uint32_t) raw_data_buffer[4] << 4) | (raw_data_buffer[5] >> 4);
press_raw = ((uint32_t) raw_data_buffer[0] << 12) | ((uint32_t) raw_data_buffer[1] << 4) | (raw_data_buffer[2] >> 4);
hum_raw = ((uint32_t) raw_data_buffer[6] << 8) | raw_data_buffer[7];
// apply corrections and save to sensor data struct
sensor_data->temperature = (float) bme280_compensate_temperature(compensation_params, temp_raw) / 100;
sensor_data->pressure = (float) bme280_compensate_pressure(compensation_params, press_raw) / 25600;
sensor_data->humidity = (float) bme280_compensate_humidity(compensation_params, hum_raw) / 1024;
return 1;
}
else return 0;
}
================================================
FILE: driver_lib/device_drivers.c
================================================
/******************************************************************************
* @file device_drivers.c
*
* @brief Monolithic function to initialize all peripheral devices connected to
* the system. Calls individual device initialization functions from the
* other source files.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include "hardware_config.h"
#include "device_drivers.h"
void driver_init(void) {
// print initialization status text for externally connected devices.
// This will only be visible if connected to the CLI UART; if CLI over USB
// is used it will not show initial boot prints because all CLI output is
// via FreeRTOS tasks/queues, and the OS kernel is not running yet.
uart_puts(UART_ID_CLI, timestamp());
uart_puts(UART_ID_CLI, "Initializing connected devices:\r\n\t {");
if (HW_USE_SPI0 && BME280_ATTACHED) {
uart_puts(UART_ID_CLI, " bme280");
if (bme280_init()) {
uart_puts(UART_ID_CLI, "[PASS]");
}
else {
uart_puts(UART_ID_CLI, "[FAIL]");
}
}
if (HW_USE_I2C0 && MCP4725_ATTACHED) {
uart_puts(UART_ID_CLI, " mcp4725");
if (mcp4725_init()) {
uart_puts(UART_ID_CLI, "[PASS]");
}
else {
uart_puts(UART_ID_CLI, "[FAIL]");
}
}
uart_puts(UART_ID_CLI, " }\r\n");
}
================================================
FILE: driver_lib/device_drivers.h
================================================
/******************************************************************************
* @file device_drivers.h
*
* @brief Driver interfaces for peripheral hardware devices
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#ifndef DEVICE_DRIVERS_H
#define DEVICE_DRIVERS_H
#include
#include
/**************************
* Peripheral Configuration
***************************/
// use the below settings to specify which devices are attached to the system
// set to true to load driver and run init for this peripheral device
#define BME280_ATTACHED true
#define MCP4725_ATTACHED true
/**
* @brief Initialize all attached peripheral devices.
*
* Single function to initialize all peripheral devices attached to the system,
* to be run once after all onboard hardware interfaces have been brought up
* with hardware_init().
*
* @param none
*
* @return nothing
*/
void driver_init(void);
/************************************
* Bosch BME280 Environmental Sensor
*************************************/
// bme280 compensation parameters typedef
// these parameters are written into device ROM at manufacture
typedef struct bme280_compensation_params_t {
struct {
uint16_t T1;
int16_t T2, T3;
} temp_comp;
struct {
uint16_t P1;
int16_t P2, P3, P4, P5, P6, P7, P8, P9;
} press_comp;
struct {
uint8_t H1, H3;
int8_t H6;
int16_t H2, H4, H5;
} hum_comp;
int32_t temp_fine;
} bme280_compensation_params_t;
// bme280 calibrated sensor readings typedef
typedef struct bme280_sensor_data_t {
float temperature;
float pressure;
float humidity;
} bme280_sensor_data_t;
// global storage for bme280 compensation params, read out once in bme280_init()
extern bme280_compensation_params_t bme280_compensation_params_glob;
/**
* @brief Initialize the BME280 device.
*
* Initialize the BME20 environmental sensor device by writing any pre-configuration
* parameters and setting up the device to start polling sensor values.
*
* @param none
*
* @return 1 if initialization success, 0 if failure
*/
int bme280_init(void);
/**
* @brief Read out the factory compensation values.
*
* Read out the factory compensation values that are stored in device ROM during
* manufacture, to be applied to the raw sensor readings for calibrated results.
* This only needs to be done once, and then applied to all further sensor readings.
*
* @param compensation_params pointer to the compensation data structure to store values
*
* @return 1 if compensation data successfully read, 0 if failed to read
*/
int bme280_read_compensation_parameters(bme280_compensation_params_t *compensation_params);
/**
* @brief Read BME280 sensor data.
*
* Reads out current sensor data readings from the BME280 and applies the calibration
* parameters previously read out from the device with bme280_read_compensation_parameters.
*
* @param compensation_params pointer to the compensation values data structure
* @param sensor_data pointer to the structure to hold the calibrated sensor readings
*
* @return 1 if sensor data read successfully, 0 if failed to read
*/
int bme280_read_sensors(bme280_compensation_params_t *compensation_params, bme280_sensor_data_t *sensor_data);
/************************************************
* Microchip MCP4725 Digital-to-Analog Converter
*************************************************/
// MCP4725 specific settings
// VDD rail voltage - defines scaling for the 12-bit DAC value
#define MCP4725_VDD 3.3
// I2C address (hex)
#define MCP4725_I2C_ADDR 0x60
/**
* @brief Initialize the MCP4725 device.
*
* Initiate communication with the MCP4725 and verify that the device is
* connected and operational.
*
* @param none
*
* @return 1 if initialization success, 0 if failure
*/
int mcp4725_init(void);
/**
* @brief Set the voltage of the DAC.
*
* Sets the output voltage of the MCP4725 by writing the 12-bit value over I2C.
* The conversion is based on the MCP4725_VDD parameter definition. The value can
* be written in "fast mode" (don't save to EEPROM), or, one can choose to store
* the value to built-in EEPROM so that the DAC setting is preserved through
* power cycles.
*
* @param voltage desired output voltage from 0 - VCC
* @param save_in_eeprom also write the DAC setting into internal NVM
*
* @return 1 if successfully set, 0 if fail
*/
int mcp4725_set_voltage(float voltage, bool save_in_eeprom);
/**
* @brief Get the current DAC voltage
*
* Gets the current value of the DAC setting in volts. This is the real-time
* setting and does not reflect what might be stored in built-in EEPROM.
*
* @param none
*
* @return floating-point voltage setting of the DAC (negative indicates error)
*/
float mcp4725_get_voltage(void);
#endif /* DEVICE_DRIVERS_H */
================================================
FILE: driver_lib/mcp4725.c
================================================
/******************************************************************************
* @file mcp4725.c
*
* @brief Hardware interface driver functions for the Microchip MCP4725 12-bit
* Digital-to-Analog Converter (DAC) with built-in EEPROM.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include "device_drivers.h"
#include "hardware_config.h"
int mcp4725_init(void) {
uint8_t rx_byte;
// read a single byte to check for MCP4725 device code
if (i2c0_read(MCP4725_I2C_ADDR, &rx_byte, 1) == 1) {
rx_byte = rx_byte >> 4;
if (rx_byte == 0xC) { // check for 4-bit device code 1100
return 1;
}
else return 0;
}
}
int mcp4725_set_voltage(float voltage, bool save_in_eeprom) {
uint16_t dac_setting = (uint16_t)(4095 * voltage / MCP4725_VDD);
uint8_t dac_write_data[3];
if (save_in_eeprom) {
dac_write_data[0] = 0x60; // specify write to DAC register and EEPROM in 1st byte
dac_write_data[1] = (uint8_t) (dac_setting >> 4); // upper 8 bits of DAC value goes in 2nd byte
dac_write_data[2] = (uint8_t) (dac_setting << 4); // lower 4 bits goes in top nibble of 3rd byte
if (i2c0_write(MCP4725_I2C_ADDR, dac_write_data, 3) == 3) {
return 1;
}
else return 0;
}
else {
dac_write_data[0] = (uint8_t) (dac_setting >> 8); // upper 4 bits of DAC value goes in 1st byte
dac_write_data[1] = (uint8_t) dac_setting; // lower 8 bits of DAC value goes into 2nd byte
if (i2c0_write(MCP4725_I2C_ADDR, dac_write_data, 2) == 2) {
return 1;
}
else return 0;
}
}
float mcp4725_get_voltage(void) {
uint16_t dac_setting;
uint8_t dac_read_data[3];
if (i2c0_read(MCP4725_I2C_ADDR, dac_read_data, 3) == 3) {
dac_setting = (uint16_t) (dac_read_data[1] << 4); // 2nd byte read is 8 MSBs of the current DAC setting
dac_setting = dac_setting | (uint16_t) (dac_read_data[2] >> 4); // top nibble of 3rd byte read has 4 LSBs of DAC setting
float voltage = (float) MCP4725_VDD * (float) dac_setting / 4095;
return voltage;
}
else return -1;
}
================================================
FILE: hardware/rp2xxx/CMakeLists.txt
================================================
# add source files to the top-level project
target_sources(${PROJ_NAME} PRIVATE
hardware_config.c
hw_gpio.c
hw_uart.c
hw_i2c.c
hw_spi.c
hw_adc.c
hw_usb.c
onboard_led.c
onboard_flash.c
hw_watchdog.c
hw_registers.c
hw_reset.c
hw_clocks.c
hw_cores.c
hw_versions.c
)
# optional features
if (ENABLE_WIFI)
target_sources(${PROJ_NAME} PRIVATE
hw_wifi.c
hw_net.c
)
endif()
function(hardware_build_extra)
# initialize the Pico/RP2040 SDK
pico_sdk_init()
# Pass these parameters to the preprocessor if using specific board/chip types
# since some HW config needs to change
if(PICO_PLATFORM STREQUAL "rp2040") # the Pico SDK import will auto set PICO_PLATFORM based on PICO_BOARD
target_compile_definitions(${PROJ_NAME} PUBLIC -DUSING_RP2040)
target_compile_definitions(cli PUBLIC -DUSING_RP2040)
endif()
if(PICO_PLATFORM STREQUAL "rp2350-arm-s") # the Pico SDK import will auto set PICO_PLATFORM based on PICO_BOARD
target_compile_definitions(${PROJ_NAME} PUBLIC -DUSING_RP2350)
target_compile_definitions(cli PUBLIC -DUSING_RP2350)
endif()
# Pass optional build options to the preprocessor for various code inclusions
if(ENABLE_WIFI)
target_compile_definitions(${PROJ_NAME} PUBLIC -DHW_USE_WIFI
-DCYW43_HOST_NAME=\"${HOSTNAME}-${BOARD}\")
target_compile_definitions(cli PUBLIC -DHW_USE_WIFI
-DHOST_NAME=\"${HOSTNAME}\")
if(ENABLE_HTTPD)
# defines used for code inclusion
target_compile_definitions(${PROJ_NAME} PUBLIC -DENABLE_HTTPD)
target_compile_definitions(cli PUBLIC -DENABLE_HTTPD)
# pico-sdk function for building http content into a library
pico_add_library(lwip_httpd_content)
pico_set_lwip_httpd_content(lwip_httpd_content INTERFACE
${PROJECT_SOURCE_DIR}/hardware/rp2xxx/net_inc/httpd_content/404.html
${PROJECT_SOURCE_DIR}/hardware/rp2xxx/net_inc/httpd_content/index.shtml
${PROJECT_SOURCE_DIR}/hardware/rp2xxx/net_inc/httpd_content/test.shtml
)
endif()
endif()
# disable Pico STDIO - interacting with CLI will be done via RTOS task queue only, no printf
pico_enable_stdio_usb(${PROJ_NAME} 0)
pico_enable_stdio_uart(${PROJ_NAME} 0)
# generate UF2, dissassembly and map files, etc
pico_add_extra_outputs(${PROJ_NAME})
endfunction()
================================================
FILE: hardware/rp2xxx/hardware_config.c
================================================
/******************************************************************************
* @file hardware_config.c
*
* @brief Monolithic function to initialize all hardware on the device. Calls
* individual hardware initialization functions from the other source
* files.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include
#include "hardware_config.h"
#ifdef HW_USE_WIFI
#include "hw_wifi.h"
#endif
#ifdef USING_RP2040
#include "hardware/regs/vreg_and_chip_reset.h"
#elif USING_RP2350
#include "hardware/structs/powman.h"
#include "hardware/regs/powman.h"
#endif
#ifdef HAS_CYW43
#include "pico/cyw43_arch.h"
#endif
// get the last reset reason and store it to global variable
reset_reason_t last_reset_reason;
void hardware_init(void) {
// mutexes for accessing hardware peripherals (created within each hw init function)
SemaphoreHandle_t gpio_mutex = NULL;
SemaphoreHandle_t cli_uart_mutex = NULL;
SemaphoreHandle_t aux_uart_mutex = NULL;
SemaphoreHandle_t i2c0_mutex = NULL;
SemaphoreHandle_t spi0_mutex = NULL;
SemaphoreHandle_t onboard_flash_mutex = NULL;
SemaphoreHandle_t adc_mutex = NULL;
SemaphoreHandle_t usb_mutex = NULL;
// initialize the uart for cli/microshell first for status prints
cli_uart_init();
// get the last reset reason type & string
last_reset_reason = get_reset_reason(); // stored in global variable
char *reset_reason_string = get_reset_reason_string(last_reset_reason);
// get the last reset reason raw register value
#ifdef USING_RP2040
uint32_t reset_state_reg_addr = VREG_AND_CHIP_RESET_BASE + VREG_AND_CHIP_RESET_CHIP_RESET_OFFSET;
#elif USING_RP2350
uint32_t reset_state_reg_addr = POWMAN_BASE + POWMAN_CHIP_RESET_OFFSET;
#endif
char reset_reason_raw_reg[30];
sprintf(reset_reason_raw_reg, "Reset Register: 0x%08X\r\n", (unsigned int)read_chip_register(reset_state_reg_addr));
// print the last reset reason
uart_puts(UART_ID_CLI, timestamp());
uart_puts(UART_ID_CLI, reset_reason_string);
uart_puts(UART_ID_CLI, timestamp());
uart_puts(UART_ID_CLI, reset_reason_raw_reg);
// print hardware initialization status text. This will only be visible if
// connected to the CLI UART; if CLI over USB is used it will not show initial
// boot prints because all CLI output is via FreeRTOS tasks/queues, and the
// OS kernel is not running yet.
uart_puts(UART_ID_CLI, timestamp());
uart_puts(UART_ID_CLI, "Configuring MCU peripherals:\r\n\t { ");
// initialize the auxilliary uart
if (HW_USE_AUX_UART) {
aux_uart_init();
uart_puts(UART_ID_CLI, "aux_uart ");
}
// initialize i2c peripheral(s)
if (HW_USE_I2C0) {
i2c0_init();
uart_puts(UART_ID_CLI, "i2c0 ");
}
// initialize spi peripheral(s)
if (HW_USE_SPI0) {
spi0_init();
uart_puts(UART_ID_CLI, "spi0 ");
}
// initialize the onboard LED gpio (if not a Pico W board)
if (HW_USE_ONBOARD_LED) {
onboard_led_init();
uart_puts(UART_ID_CLI, "led ");
}
// initialize the ADC(s)
if (HW_USE_ADC) {
adcs_init();
uart_puts(UART_ID_CLI, "adc ");
}
// initialize USB hardware
if (HW_USE_USB | CLI_USE_USB) {
usb_device_init();
uart_puts(UART_ID_CLI, "usb ");
}
// initialize GPIO
if (HW_USE_GPIO) {
gpio_init_all();
uart_puts(UART_ID_CLI, "gpio ");
}
// initialize onboard flash
if (HW_USE_ONBOARD_FLASH) {
onboard_flash_init();
uart_puts(UART_ID_CLI, "flash ");
}
uart_puts(UART_ID_CLI, "}\r\n");
// initialize the wireless module
// if wifi is enabled, this is done in the FreeRTOS task instead, since
// the init function uses the pico-sdk lwip/FreeRTOS port in this case
#ifdef HW_USE_WIFI
if (HW_USE_WIRELESS && HAS_CYW43 && !HW_USE_WIFI) {
if(hw_wifi_init()) {
uart_puts(UART_ID_CLI, timestamp());
uart_puts(UART_ID_CLI, "Failed to initialize CYW43 hardware.\r\n");
} else {
uart_puts(UART_ID_CLI, timestamp());
uart_puts(UART_ID_CLI, "Initialized onboard wireless module\r\n");
}
}
#endif /* HW_USE_WIFI */
}
================================================
FILE: hardware/rp2xxx/hardware_config.h
================================================
/******************************************************************************
* @file hardware_config.h
*
* @brief Settings and function prototypes for all interaction with MCU
* hardware. Hardware-specific implementation resides within these
* functions - it is intended that porting to a new MCU will require
* changes only to the function implementations that reside in the
* /hardware_ folder.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#ifndef HARDWARE_CONFIG_H
#define HARDWARE_CONFIG_H
#include
#include
#include "shell.h"
#include "lfs.h"
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "FreeRTOS.h"
#include "semphr.h"
// global MCU name - used throughout the codebase to differentiate architectures
#ifdef USING_RP2350
#define MCU_NAME RP2350_Cortex_M33
#else
#define MCU_NAME RP2040_Cortex_M0
#endif
// stringify helper - use xstr() to convert #define to a usable string
#define str(s) # s
#define xstr(s) str(s)
/************************
* TABLE OF CONTENTS
*************************
search file for these keywords including the leading '*' to find section
* GPIO
* CLI UART
* Auxilliary UART
* I2C0 Master
* SPI0 Master
* On-board LED
* Watchdog Timer
* Chip Reset
* System Clocks/Timers
* System MCU Cores
* Chip Versions
* Chip Registers
* Onboard Flash
* ADC - Analog-to-Digital Coverters
* USB (TinyUSB) CDC
* Wireless (CYW43)
/**
* @brief Initialize all used hardware peripherals.
*
* Single function to initialize & configure hardware & peripherals on the device,
* to be run once at boot.
*
* @param none
*
* @return nothing
*/
void hardware_init(void);
/************************
* GPIO
************************/
/*GPIO Settings
*
* Each GPIO pin should have its own entry as `GPIO_x_` in
* the following parameter lists:
* MCU_ID
* DIRECTION
* PULL
* IRQ_EN
* After each set of defines, an ordered list is created with each of the GPIO
* indexes, using the following example:
* #define GPIO_MCU_IDS GPIO_0_MCU_ID, \ <-- note the backslash for preprocessor line break
* GPIO_1_MCU_ID, \
* GPIO_2_MCU_ID, \
* GPIO_3_MCU_ID
* This allows population in the array struct definition in hw_gpio.c without
* needing to update that source file. Changes to add remove GPIO only need be
* made here.
*
* Please note: GPIO index in all functions use the `GPIO_x` index number and not
* physcal MCU index or pin # for abstraction from the chip.
*******************************************************************************/
// Enable GPIO peripheral - setting to false will disable (not initialized at boot)
#define HW_USE_GPIO true
// define the total number of GPIO pins used here
// this should match the highest `GPIO_x` defines below plus one
#define GPIO_COUNT 4
// GPIO pin IDs - note that 'GPIO_x' can correspond to any unused GPIO# on the MCU
#define GPIO_0_MCU_ID 10
#define GPIO_1_MCU_ID 11
#define GPIO_2_MCU_ID 12
#define GPIO_3_MCU_ID 13
// add the above pin IDs to this ordered list for expansion in the settings array structure
#define GPIO_MCU_IDS GPIO_0_MCU_ID, \
GPIO_1_MCU_ID, \
GPIO_2_MCU_ID, \
GPIO_3_MCU_ID
// set mode as input (GPIO_IN) or output (GPIO_OUT)
#define GPIO_0_DIRECTION GPIO_OUT
#define GPIO_1_DIRECTION GPIO_OUT
#define GPIO_2_DIRECTION GPIO_IN
#define GPIO_3_DIRECTION GPIO_IN
// add the above pin directions to this ordered list for expansion in the settings array structure
#define GPIO_DIRECTIONS GPIO_0_DIRECTION, \
GPIO_1_DIRECTION, \
GPIO_2_DIRECTION, \
GPIO_3_DIRECTION
// define GPIO pull state possible values, don't touch these!!
#define GPIO_PULL_DISABLED 0 // don't touch
#define GPIO_PULL_UP 1 // don't touch
#define GPIO_PULL_DOWN 2 // don't touch
// set pull mode of pins (GPIO_PULL_UP, GPIO_PULL_DOWN, GPIO_PULL_DISABLED)
// note this only applies to inputs, outputs on RP2040 are push/pull
#define GPIO_0_PULL GPIO_PULL_DISABLED
#define GPIO_1_PULL GPIO_PULL_DISABLED
#define GPIO_2_PULL GPIO_PULL_DISABLED
#define GPIO_3_PULL GPIO_PULL_DISABLED
// add the above GPIO pulls to this ordered list for expansion in the settings array structure
#define GPIO_PULLS GPIO_0_PULL, \
GPIO_1_PULL, \
GPIO_2_PULL, \
GPIO_3_PULL
// for inputs, should an ISR be associated to a state change (true/false)
// note that only one ISR can be configured per processor core
// currently the only ISR defined is 'gpio_process' - see function prototype below (can be customized)
#define GPIO_0_IRQ_EN false
#define GPIO_1_IRQ_EN false
#define GPIO_2_IRQ_EN false
#define GPIO_3_IRQ_EN false
// add the above IRQ enables to this ordered list for expansion in the settings array structure
#define GPIO_IRQS GPIO_0_IRQ_EN, \
GPIO_1_IRQ_EN, \
GPIO_2_IRQ_EN, \
GPIO_3_IRQ_EN
// GPIO settings structure, used to initialize all GPIO pins and interract with
// them at runtime.
typedef struct gpio_settings_t {
uint gpio_mcu_id[GPIO_COUNT];
bool gpio_direction[GPIO_COUNT];
uint8_t gpio_pull[GPIO_COUNT];
bool gpio_irq_en[GPIO_COUNT];
} gpio_settings_t;
// global GPIO settings stored here to be accessed in other files
extern struct gpio_settings_t gpio_settings;
// GPIO event structure, used to hold GPIO IRQ events
typedef struct gpio_event_t {
uint gpio;
uint32_t event_mask;
uint64_t timestamp;
} gpio_event_t;
// global GPIO event holder for last IRQ event that occured
extern struct gpio_event_t gpio_event;
// global GPIO mutex
extern SemaphoreHandle_t gpio_mutex;
/**
* @brief GPIO process ISR callback.
*
* Generic Interrupt Service Routine to process a GPIO state change IRQ.
* This is a simple routine that identifies the change that happened and updates
* a global variable. Note that on RP2040, only one ISR can be associated to a
* GPIO IRQ event per core.
*
* @param gpio which GPIO caused this interrupt
* @param event_mask which events caused this interrupt. See pico SDK gpio.h 'gpio_irq_level' for details
*
* @return nothing
*/
void gpio_process(uint gpio, uint32_t event_mask);
/**
* @brief Initialize all used GPIO.
*
* Initialize all of the GPIO pins defined in the gpio_settings configuration
* structure, based on the configuration definitions above.
*
* @param none
*
* @return nothing
*/
void gpio_init_all(void);
/**
* @brief Get the value of a single GPIO pin.
*
* Gets the value (0, 1) of a single GPIO pin defined by its ID - GPIO_x_MCU_ID
*
* @param gpio_id GPIO ID x given by GPIO_x_MCU_ID definition
*
* @return GPIO pin state (0, 1)
*/
bool gpio_read_single(uint gpio_id);
/**
* @brief Set the value of a single GPIO pin.
*
* Sets the value (0, 1) of a single GPIO pin defined by its ID - GPIO_x_MCU_ID
*
* @param gpio_id GPIO ID x given by GPIO_x_MCU_ID definition
*
* @return nothing
*/
void gpio_write_single(uint gpio_id, bool value);
/**
* @brief Read values of all used GPIO pins.
*
* Reads the current state of all GPIO pins that are defined as GPIO_IN.
* The return value is a 32-bit number with LSB representing GPIO_0 as defined
* in the settings above, and MSB representing GPIO_31 (uint_32 return value
* supports up to 32 GPIO pins). Any bits representing pins that are set to
* GPIO_OUT are ignored. The global gpio_settings structure can be used to
* iterate through used GPIO inputs and associate them with physical pin number.
*
* @param none
*
* @return 32-bit number containing up to 32 GPIO pin state values
*/
uint32_t gpio_read_all(void);
/**
* @brief Write the values of all used GPIO pins.
*
* Writes the value of all GPIO pins that are defined as GPIO_OUT, given a 32-bit
* number representing up to 32 individual GPIO pins. Individual bits representing
* single GPIO outputs can be shifted in based on the pin indexes defined in
* the global gpio_settings structure. Any bits that correspond to pins that are
* set to GPIO_IN are ignored.
*
* @param gpio_states 32-bit number containing up to 32 GPIO values to write
*
* @return nothing
*/
void gpio_write_all(uint32_t gpio_states);
/************************
* CLI UART
*************************/
// CLI UART Settings
#define UART_ID_CLI uart0
#define UART_BAUD_RATE_CLI 115200
#define UART_DATA_BITS_CLI 8
#define UART_STOP_BITS_CLI 1
#define UART_PARITY_CLI UART_PARITY_NONE
#define UART_TX_PIN_CLI 0
#define UART_RX_PIN_CLI 1
// global CLI UART mutex
extern SemaphoreHandle_t cli_uart_mutex;
/**
* @brief Initialize the CLI UART.
*
* Initialization routine for the UART peripheral used for the
* Command Line Interface using the settings defined above.
*
* @param none
*
* @return nothing
*/
void cli_uart_init(void);
/**
* @brief Write character to CLI UART.
*
* Write a single character to the UART used for the Command Line Interface.
* This function is used by Microshell for all CLI output.
*
* @param tx_char character to write
*
* @return 1 upon success, 0 upon failure
*/
int cli_uart_putc(char tx_char);
/**
* @brief Read character from CLI UART.
*
* Read a single character from the UART used for the Command Line Interface in
* a non-blocking fashion. This function is used by Microshell for all CLI input.
*
* @param none
*
* @return Character read from UART
*/
char cli_uart_getc(void);
/**
* @brief Print a string to the CLI UART.
*
* Print an entire string to the UART used for the Command Line Interface. This
* function can be used in the CLI outside of Microshell to print debug & status
* messages. The function will block until the entire string is written.
*
* @param print_string pointer to the string to print
*
* @return nothing
*/
void cli_uart_puts(const char *print_string);
/************************
* Auxilliary UART
*************************/
// Enable Aux UART - setting to false will disable (not initialized at boot)
#define HW_USE_AUX_UART true
// AUX UART Settings
#define UART_ID_AUX uart1
#define UART_BAUD_RATE_AUX 115200
#define UART_DATA_BITS_AUX 8
#define UART_STOP_BITS_AUX 1
#define UART_PARITY_AUX UART_PARITY_NONE
#define UART_TX_PIN_AUX 8
#define UART_RX_PIN_AUX 9
#define UART_RX_FIFO_SIZE_AUX 32 // from RP2040 datasheet (hardware limited)
// global Aux UART mutex
extern SemaphoreHandle_t aux_uart_mutex;
/**
* @brief Initialize the AUX UART.
*
* Initialization routine for the UART peripheral used for the
* auxilliary serial interface using the settings defined above.
*
* @param none
*
* @return nothing
*/
void aux_uart_init(void);
/**
* @brief Write bytes to AUX UART.
*
* Write one or more bytes to the UART used for the auxilliary serial interface.
*
* @param tx_data pointer to the data to write
* @param tx_len length in bytes of data to write
*
* @return 1 upon success, 0 upon failure
*/
int aux_uart_write(uint8_t *tx_data, size_t tx_len);
/**
* @brief Read bytes from AUX UART.
*
* Read any bytes available on the auxilliary UART in a non-blocking fashion,
* until no more bytes are immediately available or rx_len has been reached.
*
* @param rx_data pointer to the data buffer to hold the bytes received
* @param rx_len maximum number of bytes to read (buffer size)
*
* @return number of bytes received
*/
int aux_uart_read(uint8_t *rx_data, size_t rx_len);
/************************
* I2C0 Master
*************************/
// Enable I2C0 peripheral - setting to false will disable (not initialized at boot)
#define HW_USE_I2C0 true
// I2C0 Settings
#define I2C0_ID i2c0
#define I2C0_FREQ_KHZ 100
#define I2C0_SDA_PIN 20
#define I2C0_SCL_PIN 21
// global I2C0 mutex
extern SemaphoreHandle_t i2c0_mutex;
/**
* @brief Initialize I2C bus 0.
*
* Initialization routine for the I2C0 peripheral using the settings defined
* above.
*
* @param none
*
* @return nothing
*/
void i2c0_init(void);
/**
* @brief Write bytes onto I2C0 bus.
*
* Put byte(s) onto I2C bus 0 given a specific device address. It is assumed this
* is a blocking function.
*
* @param addr 7-bit address (0-127) of target device on I2C bus
* @param write_data pointer to the byte array to write onto the bus
* @param length number of bytes to write onto the bus
*
* @return number of bytes written, or -1 if no response
*/
int i2c0_write(uint8_t addr, const uint8_t *write_data, size_t length);
/**
* @brief Read bytes off of I2C0 bus.
*
* Read byte(s) from a given device adress on I2C bus 0. It is assumed this is a
* blocking function.
*
* @param addr 7-bit address (0-127) of target device on I2C bus
* @param read_data pointer to the byte array to store the data read from the bus
* @param length number of bytes to read from the bus
*
* @return number of bytes read, or -1 if no response
*/
int i2c0_read(uint8_t addr, uint8_t *read_data, size_t length);
/************************
* SPI0 Master
*************************/
// Enable SPI0 peripheral - setting to false will disable (not initialized at boot)
#define HW_USE_SPI0 true
// SPI0 Settings
#define SPI0_ID spi0
#define SPI0_FREQ_KHZ 500
#define SPI0_MISO_PIN 4
#define SPI0_MOSI_PIN 3
#define SPI0_CLK_PIN 2
#define SPI0_CS_PIN_DEFAULT 5
// Target/peripheral device settings -
// used to define a target device by the chip select it is attached to, and the
// register address that can be interrogated for the expected device ID to
// validate communication
#define SPI0_TARGET_DEV_0_CS SPI0_CS_PIN_DEFAULT
#define SPI0_TARGET_DEV_0_ID 0x60
#define SPI0_TARGET_DEV_0_ID_REG 0xD0
// more TARGET_DEV entries and associated chip selects can be added to expand this list
// global SPI0 mutex
extern SemaphoreHandle_t spi0_mutex;
/**
* @brief Initialize SPI bus 0.
*
* Initialization routine for the SPI0 peripheral using the settings defined
* above.
*
* @param none
*
* @return nothing
*/
void spi0_init(void);
/**
* @brief Write a byte into a SPI0 register address.
*
* Writes a single byte to a target device identified by the chip select pin, into
* the register at the given address.
*
* @param cs_pin master pin number for the chip select line connected to target device
* @param reg_addr 8-bit register address to write to
* @param data_byte single-byte of data to write into target register
*
* @return 1 if success, 0 if failure
*/
int spi0_write_register(uint8_t cs_pin, uint8_t reg_addr, uint8_t data_byte);
/**
* @brief Read byte(s) from a SPI0 register address.
*
* Reads one or more bytes from a target device identified by the chip select pin,
* from the given register address. Multiple successive registers can be read in
* series by specifying the read length. This is a blocking read.
*
* @param cs_pin master pin number for the chip select line connected to target device
* @param reg_addr 8-bit register address to read from
* @param read_buf pointer to the byte array to store the data read from target
* @param len number of successive register locations to read from
*
* @return number of bytes read
*/
int spi0_read_registers(uint8_t cs_pin, uint8_t reg_addr, uint8_t *read_buf, uint16_t len);
/************************
* On-board LED
*************************/
// Enable onboard LED - setting to false will disable (not initialized at boot)
// note - if using a Pico W board, the onboard LED is connected to the CYW43 radio -
// this would need the WiFi chip initialized, which we do not currently do. Thus
// the onboard LED will be default disabled in that case.
#define HW_USE_ONBOARD_LED true
// On-board LED Settings
#define PIN_NO_ONBOARD_LED 25 // does not apply to Pico W board (connected to CYW43)
/**
* @brief Initialization routine for the onboard LED.
*
* This function initializes the GPIO used as the driver for the onboard LED.
*
* @param none
*
* @return nothing
*/
void onboard_led_init(void);
/**
* @brief Sets the state of the onboard LED.
*
* This function sets the value of the onboard LED GPIO to enable (turn ON) or
* disable (turn OFF) the LED.
*
* @param led_state 0 (disabled) or 1 (enabled)
*
* @return nothing
*/
void onboard_led_set(bool led_state);
/**
* @brief Gets the state of the onboard LED.
*
* This function gets the value of the onboard LED GPIO to determine if the LED
* is enabled (ON) or disabled (OFF).
*
* @param none
*
* @return 0 (disabled) or 1 (enabled)
*/
bool onboard_led_get(void);
/************************
* Watchdog Timer
*************************/
// Watchdog Timer Settings
#define WATCHDOG_DELAY_MS 5000 // default watchdog timer delay
#define WATCHDOG_DELAY_REBOOT_MS 100 // delay for reboot function
/**
* @brief Enables the watchdog timer.
*
* This function enables the hardware watchdog timer at the specified interval.
* On RP2040, there will be a standard soft reboot when watchdog timer expires.
*
* @param delay_ms millisecond interval for the watchdog before reboot
*
* @return nothing
*/
void watchdog_en(uint32_t delay_ms);
/**
* @brief Disables the watchdog timer.
*
* This function disables the hardware watchdog timer.
*
* @param none
*
* @return nothing
*/
void watchdog_dis(void);
/**
* @brief Reset the watchdog timer.
*
* Resets the watchdog timer to the start value given when the timer was started
* with watchdog_en(). One must kick the dog before this timer expires to prevent
* the watchdog expiration action (default to soft reboot). Be assured that this
* source code in no way condones kicking dogs or any other life form for that
* matter.
*
* @param none
*
* @return nothing `
*/
void watchdog_kick(void);
/**
* @brief Force a reboot.
*
* Forces a reboot of the MCU by waiting for watchdog timeout.
*
* @param none
*
* @return nothing
*/
void force_watchdog_reboot(void);
/************************
* Chip Reset
*************************/
// Reset reason types
typedef enum {POWERON, // normal power-on reset
GLITCH, // power supply glitch reset
BROWNOUT, // brownout reset
WATCHDOG, // watchdog timeout reset
FORCED, // application-requested reset
PIN, // external pin-toggled reset
DOUBLETAP, // double-tap external pin reset
DEBUGGER, // attached debugger reset
UNKNOWN // reset reason could not be detected
} reset_reason_t;
// Global reset reason type - set at boot
extern reset_reason_t last_reset_reason;
/**
* @brief Get the reset reason.
*
* This function will return the reason for the last system reset, i.e. POR,
* watchdog, program-requested, reset pin, or debugger.
*
* @param none
*
* @return reset reason given by reset_reason_t enum
*/
reset_reason_t get_reset_reason(void);
/**
* @brief Get the reset reason as a string.
*
* This function is a wrapper for get_reset_reason() which returns a human-readable
* string that can be used for printing to the CLI.
*
* @param reset_reason reset reason given by reset_reason_t enum, provided by get_reset_reason()
*
* @return pointer to the reset reason string
*/
char* get_reset_reason_string(reset_reason_t reset_reason);
/**
* @brief Reset chip to the bootloader.
*
* Forces a reset of the MCU into the bootloader so that firmware can be updated.
* On RP2040 this is the built-in DFU mode.
*
* @param none
*
* @return nothing
*/
void reset_to_bootloader(void);
/************************
* System Clocks/Timers
*************************/
/**
* @brief Get the system clock frequency.
*
* Returns the current frequency of the internal system clock that the MCU has
* determined it is using, in Hz.
*
* @param none
*
* @return system clock frequency, Hz
*/
uint32_t get_sys_clk_hz(void);
/**
* @brief Get system timer value.
*
* Returns the current system timer value in microseconds. Size uint64 means the
* timer will not roll over for 584,942 years. When using the function to print
* the time, use snprintf with 14 bytes reserved for ASCII timer value to get ~3 years
* before rollover (13 bytes for ~0.3 years, 15 bytes for ~30 years, etc.)
*
* @param none
*
* @return timer value, microseconds
*/
uint64_t get_time_us(void);
/**
* @brief Wait for the specified amount of microseconds.
*
* Waits (delays) execution of further code for the specified number of microseconds.
* Similar to running many NOOPs to waste cycles for a specified amount of time.
*
* @param delay_us number of microseconds to delay
*
* @return nothing
*/
void wait_here_us(uint64_t delay_us);
/************************
* System MCU Cores
*************************/
/**
* @brief Get the core that is executing.
*
* Returns the current core number that this function was executed on when
* running on a multi-core MCU.
*
* @param none
*
* @return core number
*/
uint8_t get_core(void);
/************************
* Chip Versions
*************************/
/**
* @brief Get the silicon version number.
*
* Returns the integer value version number of the MCU silicon.
*
* @param none
*
* @return version number
*/
uint8_t get_chip_version(void);
/**
* @brief Get the ROM version number.
*
* Returns the integer value version number of the MCU's ROM.
*
* @param none
*
* @return version number
*/
uint8_t get_rom_version(void);
/************************
* Chip Registers
*************************/
/**
* @brief Read a chip register.
*
* Reads and returns a single 32-bit register from the MCU's memory-mapped
* register space.
*
* @param reg_addr address of the register to read
*
* @return 32-bit value of the register
*/
uint32_t read_chip_register(uint32_t reg_addr);
/************************
* Onboard Flash
*************************/
// Enable onboard flash - setting to false will disable program access to flash
#define HW_USE_ONBOARD_FLASH true
// Onboard Flash Settings
#define FLASH0_FS_SIZE (256 * 1024) // size of the 'flash0' filesystem (intended for littlefs to manage)
#define PATHNAME_MAX_LEN 32 // maximum string length of path+filename on the filesystem
#define FILE_SIZE_MAX BUF_OUT_SIZE // maximum size of a single file in bytes - set to shell output buffer size so entire files can be dumped
#define FLASH0_BLOCK_SIZE FLASH_SECTOR_SIZE // "block" size in littlefs terms is "sector" size in RP2040 terms
#define FLASH0_PAGE_SIZE FLASH_PAGE_SIZE // littlefs page size is equal to flash page size
#define FLASH0_CACHE_SIZE FLASH_PAGE_SIZE // read/write cache sizes are equal to a page
#define FLASH0_LOOKAHEAD_SIZE 32 // lookahead buffer size for tracking block allocation
#define FLASH0_BLOCK_CYCLES 500 // max number of erase cycles for a block (for wear leveling)
// Onboard flash usage detail structure
typedef struct flash_usage_t {
int flash_total_size;
int program_used_size;
int fs_reserved_size;
int flash_free_size;
} flash_usage_t;
// global onboard flash mutex
extern SemaphoreHandle_t onboard_flash_mutex;
/**
* @brief Initialize onboard flash memory.
*
* This function enables usage of the onboard flash memory (this is the same NVM
* used for program storage). Note that all this function does at this time is
* create the mutex used for onboard flash access, since flash I/O is already
* technically available at runtime.
*
* @param none
*
* @return nothing
*/
void onboard_flash_init(void);
/**
* @brief Read from onboard flash.
*
* This function reads a given address range of "onboard" flash (the same NVM
* used for program storage). Argument structure conforms to littlefs, however
* the function may also be used standalone.
*
* @param c littlefs configuration, can populate with a dummy if using outside of littlefs
* @param block address of the flash block (sector) to read from
* @param offset byte offset within the block of read start
* @param buffer pointer to the read buffer
* @param size size of data to read
*
* @return 0 upon success
*/
int onboard_flash_read(const struct lfs_config *c, uint32_t block, uint32_t offset, void* buffer, uint32_t size);
/**
* @brief Write to onboard flash.
*
* This function writes data to "onboard" flash (the same NVM used for program
* storage). Note that the block of flash written to must have been previously
* erased. Argument structure conforms to littlefs, however the function may also
* be used standalone.
*
* @param c littlefs configuration, can populate with a dummy if using outside of littlefs
* @param block address of the flash block (sector) to write to
* @param offset byte offset within the block of write start
* @param buffer pointer to the buffer containing data to write
* @param size size of data to write
*
* @return 0 upon success
*/
int onboard_flash_write(const struct lfs_config *c, uint32_t block, uint32_t offset, const void* buffer, uint32_t size);
/**
* @brief Erase onboard flash.
*
* This function erases a block (sector) of "onboard" flash (the same NVM used
* for program storage). Argument structure conforms to littlefs, however the
* function may also be used standalone.
*
* @param c littlefs configuration, can populate with a dummy if using outside of littlefs
* @param block address of the flash block (sector) to erase
*
* @return 0 upon success
*/
int onboard_flash_erase(const struct lfs_config *c, uint32_t block);
/**
* @brief Sync the state of onboard flash.
*
* This is a dummy sync function since littlefs will hardfault if the sync
* function pointer is null.
*
* @param c littlefs configuration
*
* @return 0 upon success
*/
int onboard_flash_sync(const struct lfs_config *c);
/**
* @brief Get flash usage detail.
*
* Determine the application binary flash memory usage, total flash size, and
* remaining "free" space available (minus any other NVM consumers such as
* filesystem, etc)
*
* @param none
*
* @return structure containing flash usage detail
*/
flash_usage_t onboard_flash_usage(void);
/*************************************
* ADC - Analog-to-Digital Coverters
**************************************/
// Enable ADC peripheral - setting to false will disable all ADCs (not initialized at boot)
#define HW_USE_ADC true
// ADC channel settings
#define ADC0_GPIO 26 // this is ADC0
#define ADC1_GPIO 27 // this is ADC1
#define ADC2_GPIO 28 // this is ADC2
// individual ADC initialization
#define ADC0_INIT true
#define ADC1_INIT false
#define ADC2_INIT false
// ACD conversion
#define ADC_CONV_FACT (3.3f / (1 << 12)) // 3.3V reference, 12-bit resolution
// global ADC mutex
extern SemaphoreHandle_t adc_mutex;
/**
* @brief Initialize the ADC peripheral(s).
*
* Initializes the ADC peripheral. On chips with multiple ADC peripherals, each
* will be initialized seperately within this function. Note that even if an MCU
* has multiple ADC input pins, they may share a single ADC block (as is the case
* on RP2040).
*
* @param none
*
* @return nothing
*/
void adcs_init(void);
/**
* @brief Read ADC value.
*
* Read the result of the analog-to-digital conversion on the given analog input
* channel, converted to a floating-point voltage value. Note that these channels
* may share the same ADC block internal to the MCU. Here, "channel" refers to
* a specific GPIO/pin given in the settings above.
*
* @param adc_channel channel number of the ADC to read
*
* @return value of the analog-to-digital conversion (voltage reading)
*/
float read_adc(int adc_channel);
/************************
* USB (TinyUSB) CDC
*************************/
// Enable USB controller - setting to false will disable (not initialized at boot)
// note that if CLI uses USB, it will override this
#define HW_USE_USB true
// set USB mode (device)
#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE
// number of composite CDC interfaces and data buffer sizes
#define CFG_TUD_CDC 1
#define CFG_TUD_CDC_RX_BUFSIZE 64
#define CFG_TUD_CDC_TX_BUFSIZE 64
// make sure buffer size is within bounds of uint16_t
#if CFG_TUD_CDC_RX_BUFSIZE > 65535 || CFG_TUD_CDC_TX_BUFSIZE > 65535
#error "USB buffer size exceeds bounds"
#endif
// CDC interface ID for CLI over USB (if used)
#define CDC_ID_CLI 0
// USB interface data structure
typedef struct usb_iface_t {
uint8_t iface_id; // composite interface ID
bool is_conn; // connection flag
uint8_t rx_buffer[CFG_TUD_CDC_RX_BUFSIZE]; // receive buffer (bytes from host)
uint8_t tx_buffer[CFG_TUD_CDC_TX_BUFSIZE]; // transmit buffer (bytes to host)
uint16_t rx_pos; // current position in rx buffer
uint16_t tx_pos; // current position in tx buffer
} usb_iface_t;
// global USB mutex
extern SemaphoreHandle_t usb_mutex;
/**
* @brief Initialize the serial number used for USB device ID.
*
* Retrieves the unique ID (serial number) of the device and sets it as the USB
* device serial no. reported to the bus.
*
* @param none
*
* @return nothing
*/
void usb_serialno_init(void);
/**
* @brief Initialize the USB device.
*
* Initializes the USB controller in device mode using TinyUSB.
*
* @param none
*
* @return nothing
*/
void usb_device_init(void);
/**
* @brief Read bytes out of the USB device buffer.
*
* This function will check for an active connection on the given interface,
* check if there are bytes available in the device buffer, and then read out
* (copy) any available bytes into the USB interface structure instance. The
* rx_pos value in usb_iface will then be updated to reflect how many bytes are
* currently available in the rx buffer.
*
* @param usb_iface USB interface structure for a given composite device ID
*
* @return nothing
*/
void usb_read_bytes(struct usb_iface_t *usb_iface);
/**
* @brief Write bytes to the USB device buffer.
*
* This function will copy bytes from the given USB interface structure into the
* device buffer (if tx_pos > 0 indicating bytes available to be written),
* and send the bytes to the host.
*
* @param usb_iface USB interface structure for a given composite device ID
*
* @return nothing
*/
void usb_write_bytes(struct usb_iface_t *usb_iface);
/**
* @brief Write character to CLI over USB.
*
* Write a single character to the USB CDC ID used for the Command Line Interface.
* This function is used by Microshell for all CLI output if CLI_USE_USB=1 at
* compile time.
*
* @param tx_char character to write to USB
*
* @return 1 upon success, 0 upon failure
*/
int cli_usb_putc(char tx_char);
/**
* @brief Read character from CLI over USB.
*
* Read a single character from the USB CDC ID used for the Command Line Interface
* in a non-blocking fashion. This function is used by Microshell for all CLI
* input if CLI_USE_USB=1 at compile time.
*
* @param none
*
* @return Character read from USB
*/
char cli_usb_getc(void);
/**************************
* Wireless (CYW43 WiFi/BT)
***************************/
// Enable CYW43 wireless module - setting to false will disable (not initialized at boot)
// This only applies to boards with the wireless module, otherwise it is ignored.
// note that if using the onboard LED, the CYW43 must be enabled (LED is controlled by CYW43).
// Individual wireless features (WiFi/BT/etc) are individually enabled in project.cmake.
#define HW_USE_WIRELESS true
// Make sure CYW43 is enabled if using onboard LED
#if HAS_CYW43 == true && HW_USE_WIRELESS == false && HW_USE_ONBOARD_LED == true
#error "CYW43 must be enabled in hardware_config.h if using onboard LED"
#endif
// Note: due to complexity, all WiFi-specific typedefs, options, function
// declarations and documentation are contained in the hw_wifi.h file.
#endif /* HARDWARE_CONFIG_H */
================================================
FILE: hardware/rp2xxx/hw_adc.c
================================================
/******************************************************************************
* @file hw_adc.c
*
* @brief Functions for interacting with the analog-to-digital converter
* peripheral(s). The implementation of these functions is MCU-specific
* and will need to be changed if ported to a new hardware family.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include "hardware_config.h"
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "hardware/adc.h"
#include "semphr.h"
// global ADC mutex
SemaphoreHandle_t adc_mutex;
void adcs_init(void) {
// create ADC mutex
adc_mutex = xSemaphoreCreateMutex();
adc_init();
if (ADC0_INIT)
adc_gpio_init(ADC0_GPIO);
if (ADC1_INIT)
adc_gpio_init(ADC1_GPIO);
if (ADC2_INIT)
adc_gpio_init(ADC2_GPIO);
}
float read_adc(int adc_channel) {
uint16_t result = 0;
if(xSemaphoreTake(adc_mutex, 10) == pdTRUE) {
// select ADC input pin/channel (0-2)
adc_select_input(adc_channel);
// read raw value
result = adc_read();
xSemaphoreGive(adc_mutex);
}
// return floating-point voltage
return result * ADC_CONV_FACT;
}
================================================
FILE: hardware/rp2xxx/hw_clocks.c
================================================
/******************************************************************************
* @file hw_clocks.c
*
* @brief Functions for interacting with clocks and timers. The implementation
* of these functions is MCU-specific and will need to be changed if
* ported to a new hardware family.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include "hardware_config.h"
#include "hardware/clocks.h"
#include "hardware/timer.h"
uint32_t get_sys_clk_hz(void){
return clock_get_hz(clk_sys);
}
uint64_t get_time_us(void){
return time_us_64();
}
void wait_here_us(uint64_t delay_us) {
busy_wait_us(delay_us);
}
================================================
FILE: hardware/rp2xxx/hw_cores.c
================================================
/******************************************************************************
* @file hw_cores.c
*
* @brief Functions for querying information about MCU cores. The implementation
* of these functions is MCU-specific and will need to be changed if
* ported to a new hardware family.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include "hardware_config.h"
#include "pico/platform.h"
uint8_t get_core(void){
return get_core_num();
}
================================================
FILE: hardware/rp2xxx/hw_gpio.c
================================================
/******************************************************************************
* @file hw_gpio.c
*
* @brief Functions for interacting with the GPIO pins.
* The implementation of these functions is MCU-specific and will need
* to be changed if ported to a new hardware family.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include "hardware_config.h"
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "FreeRTOS.h"
#include "semphr.h"
// global GPIO settings structure, declared in hardware_config.h
// structure arrays are expanded using the GPIO list defines, so they better
// not be jacked up in that file
struct gpio_settings_t gpio_settings = {
.gpio_mcu_id = {GPIO_MCU_IDS},
.gpio_direction = {GPIO_DIRECTIONS},
.gpio_pull = {GPIO_PULLS},
.gpio_irq_en = {GPIO_IRQS}
};
// global GPIO last IRQ event storage location
struct gpio_event_t gpio_event;
// global GPIO mutex
SemaphoreHandle_t gpio_mutex;
void gpio_process(uint gpio, uint32_t event_mask) {
// populate the IRQ event structure
gpio_event.gpio = gpio;
gpio_event.event_mask = event_mask;
gpio_event.timestamp = get_time_us();
}
void gpio_init_all(void) {
// create GPIO mutex
gpio_mutex = xSemaphoreCreateMutex();
for (int gpio_num = 0; gpio_num < GPIO_COUNT; gpio_num++) {
// enable the GPIO pin
gpio_init(gpio_settings.gpio_mcu_id[gpio_num]);
// set mode to input or output
gpio_set_dir(gpio_settings.gpio_mcu_id[gpio_num], gpio_settings.gpio_direction[gpio_num]);
// if GPIO is an input, set pull direction and IRQ if applicable
if (gpio_settings.gpio_direction[gpio_num] == GPIO_IN) {
// set pull direction
if (gpio_settings.gpio_pull[gpio_num] == GPIO_PULL_UP) {
gpio_pull_up(gpio_settings.gpio_mcu_id[gpio_num]);
}
else if (gpio_settings.gpio_pull[gpio_num] == GPIO_PULL_DOWN) {
gpio_pull_down(gpio_settings.gpio_mcu_id[gpio_num]);
}
else {
gpio_disable_pulls(gpio_settings.gpio_mcu_id[gpio_num]);
}
// set up IRQ if enabled - IRQ is set to happen on both edges
// ISR function is gpio_process()
if (gpio_settings.gpio_irq_en[gpio_num] == true) {
gpio_set_irq_enabled_with_callback(gpio_settings.gpio_mcu_id[gpio_num], GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, &gpio_process);
}
}
}
}
bool gpio_read_single(uint gpio_id) {
bool gpio_value = 0;
if(xSemaphoreTake(gpio_mutex, 10) == pdTRUE) {
gpio_value = gpio_get(gpio_settings.gpio_mcu_id[gpio_id]);
xSemaphoreGive(gpio_mutex);
}
return gpio_value;
}
void gpio_write_single(uint gpio_id, bool value) {
// only write to a pin that has been configured as output
if (gpio_settings.gpio_direction[gpio_id] == GPIO_OUT) {
if(xSemaphoreTake(gpio_mutex, 10) == pdTRUE) {
gpio_put(gpio_settings.gpio_mcu_id[gpio_id], value);
xSemaphoreGive(gpio_mutex);
}
}
}
uint32_t gpio_read_all(void) {
uint32_t gpio_values = 0;
// cycle through all the configured GPIOs
for (int gpio_num = 0; gpio_num < GPIO_COUNT; gpio_num++) {
if(xSemaphoreTake(gpio_mutex, 10) == pdTRUE) {
// move the read pin state into the correct bit position corresponding to current GPIO index
gpio_values |= gpio_get(gpio_settings.gpio_mcu_id[gpio_num]) << gpio_num;
xSemaphoreGive(gpio_mutex);
}
}
return gpio_values;
}
void gpio_write_all(uint32_t gpio_states) {
bool gpio_value;
for (int gpio_num = 0; gpio_num < GPIO_COUNT; gpio_num++) {
// write GPIO if it is set to an output
if (gpio_settings.gpio_direction[gpio_num] == GPIO_OUT) {
if(xSemaphoreTake(gpio_mutex, 10) == pdTRUE) {
// shift out the bit corresponding to the current GPIO index into a single bool
gpio_value = (bool) ((gpio_states >> gpio_num) & (uint32_t) 1);
gpio_put(gpio_settings.gpio_mcu_id[gpio_num], gpio_value);
xSemaphoreGive(gpio_mutex);
}
}
}
}
================================================
FILE: hardware/rp2xxx/hw_i2c.c
================================================
/******************************************************************************
* @file hw_i2c.c
*
* @brief Functions for configuring and interacting with the I2C peripherals.
* The implementation of these functions is MCU-specific and will need
* to be changed if ported to a new hardware family.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include "hardware_config.h"
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "hardware/i2c.h"
#include "semphr.h"
// global I2C0 mutex
SemaphoreHandle_t i2c0_mutex;
void i2c0_init(void) {
// create I2C0 mutex
i2c0_mutex = xSemaphoreCreateMutex();
i2c_init(I2C0_ID, I2C0_FREQ_KHZ * 1000);
gpio_set_function(I2C0_SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(I2C0_SCL_PIN, GPIO_FUNC_I2C);
gpio_pull_up(I2C0_SDA_PIN);
gpio_pull_up(I2C0_SCL_PIN);
}
int i2c0_write(uint8_t addr, const uint8_t *write_data, size_t length) {
i2c_inst_t *i2c = I2C0_ID;
int bytes_written = 0;
// write bytes if mutex is obtained
if(xSemaphoreTake(i2c0_mutex, 10) == pdTRUE) {
bytes_written = i2c_write_blocking(i2c, addr, write_data, length, false);
xSemaphoreGive(i2c0_mutex);
}
// check for Pico SDK-specific error and replace with -1
if(bytes_written == PICO_ERROR_GENERIC) {
bytes_written = -1;
}
return bytes_written;
}
int i2c0_read(uint8_t addr, uint8_t *read_data, size_t length) {
i2c_inst_t *i2c = I2C0_ID;
int bytes_read = 0;
// read bytes if mutex is obtained
if(xSemaphoreTake(i2c0_mutex, 10) == pdTRUE) {
bytes_read = i2c_read_blocking(i2c, addr, read_data, length, false);
xSemaphoreGive(i2c0_mutex);
}
// check for Pico SDK-specific error and replace with -1
if(bytes_read == PICO_ERROR_GENERIC) {
bytes_read = -1;
}
return bytes_read;
}
================================================
FILE: hardware/rp2xxx/hw_net.c
================================================
/******************************************************************************
* @file hw_net.c
*
* @brief Settings, definitions, and function prototypes for various network
* stacks, much of it tied to lwIP. Hardware and SDK-specific
* implementation resides within these functions, hence its location
* within the hardware/[platform] directory.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-06-2025
*
* @copyright Copyright (c) 2025 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include "hw_net.h"
#include "hardware_config.h"
#include "version.h"
#include "FreeRTOS.h"
#include "pico/cyw43_arch.h"
#include "lwip/apps/mdns.h"
#include "lwip/apps/httpd.h"
#include "lwip/arch.h"
/************************
* lwIP mDNS responder
*************************/
// mDNS responder callback function for adding service text records
static void srv_txt(struct mdns_service *service, void *txt_userdata) {
err_t res;
LWIP_UNUSED_ARG(txt_userdata);
res = mdns_resp_add_service_txtitem(service, "path=/", 6);
LWIP_ERROR("mdns add service txt failed\n", (res == ERR_OK), return);
}
void net_mdns_init(void) {
// initialize lwIP mDNS
mdns_resp_init();
// bind mDNS to the network interface and responder text record service
mdns_resp_add_netif(&cyw43_state.netif[CYW43_ITF_STA], CYW43_HOST_NAME);
mdns_resp_add_service(&cyw43_state.netif[CYW43_ITF_STA], "bbos-httpd", "_http", DNSSD_PROTO_TCP, 80, srv_txt, NULL);
}
/**************************
* lwIP HTTPD (http server)
***************************/
#ifdef ENABLE_HTTPD
// cgi handler function example - refer to tCGIHandler typedef in lwIP httpd.h for prototype description
static const char *httpd_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]) {
// check for
if (!strcmp(pcParam[0], "led-state")) {
if (!strcmp(pcValue[0], "on")) {
onboard_led_set(1);
} else if (!strcmp(pcValue[0], "off")) {
onboard_led_set(0);
} else {
return "/404.html";
}
return "/test.shtml";
} else {
return "/404.html";
}
}
// cgi handler path assignments for any functions above
static tCGI httpd_cgi_paths[] = {
{ "/test.shtml", httpd_cgi_handler }
};
// ssi tags, handled by index # in the ssi handler function
// Note that maximum tag length is 8 characters (LWIP_HTTPD_MAX_TAG_NAME_LEN)
// There will be no warning if exceeded! The tag will simply not be processed!
static const char * httpd_ssi_tags[] = {
"bbosver",
"projinfo",
"platform",
"uptime",
"freeram",
"freeflsh",
"ledstate"
};
// ssi handler function - refer to tSSIHandler typedef in lwIP httpd.h for prototype description
// note that the integer case values in the switch need to match the index of the tag in the httpd_ssi_tags array
u16_t httpd_ssi_handler(int iIndex, char *pcInsert, int iInsertLen, uint16_t current_tag_part, uint16_t *next_tag_part) {
size_t tag_print;
switch (iIndex) {
case 0: { /* "version" */
tag_print = snprintf(pcInsert, iInsertLen, xstr(BBOS_VERSION_MAJOR) "." xstr(BBOS_VERSION_MINOR) "%c", BBOS_VERSION_MOD);
break;
}
case 1: {
tag_print = snprintf(pcInsert, iInsertLen, xstr(PROJECT_NAME) " v" xstr(PROJECT_VERSION));
break;
}
case 2: {
tag_print = snprintf(pcInsert, iInsertLen, xstr(BOARD) " - " xstr(MCU_NAME));
break;
}
case 3: { /* uptime */
// get current microsecond timer value
uint64_t current_time_us = get_time_us();
// calculate time divisions, i.e. maths
uint16_t num_seconds = (uint16_t) (current_time_us / 1E6) % 60;
uint16_t num_minutes = (uint16_t) (((current_time_us / 1E6) - num_seconds) / 60) % 60;
uint16_t num_hours = (uint16_t) (((((current_time_us / 1E6) - num_seconds) / 60) - num_minutes) / 60) % 24;
uint16_t num_days = (uint16_t) ((((((current_time_us / 1E6) - num_seconds) / 60) - num_minutes) / 60) - num_hours) / 24;
tag_print = snprintf(pcInsert, iInsertLen, "%dd %dh %dm %ds", num_days, num_hours, num_minutes, num_seconds);
break;
}
case 4: { /* freeram */
HeapStats_t *heap_stats = pvPortMalloc(sizeof(HeapStats_t)); // structure to hold heap stats results
vPortGetHeapStats(heap_stats); // get the heap stats
tag_print = snprintf(pcInsert, iInsertLen, "%.1f KB / %.1f KB",
( (float)heap_stats->xAvailableHeapSpaceInBytes / 1024 ),
( (float)(configTOTAL_HEAP_SIZE) / 1024 ));
vPortFree(heap_stats);
break;
}
case 5: { /* freeflsh <-- notice no 'a' to fit into 8 characters */
flash_usage_t flash_usage;
// get the flash usage data struct
flash_usage = onboard_flash_usage();
tag_print = snprintf(pcInsert, iInsertLen, "%.1f KB / %.1f KB",
( (float)flash_usage.flash_free_size / 1024 ),
( (float)flash_usage.flash_total_size / 1024 ));
break;
}
case 6: { /* ledstate */
tag_print = snprintf(pcInsert, iInsertLen, onboard_led_get() ? "ON" : "OFF");
break;
}
default: { /* unknown tag */
tag_print = 0;
break;
}
}
return (u16_t)tag_print;
}
void net_httpd_stack_init(void) {
// set hostname for lwIP
char hostname[sizeof(CYW43_HOST_NAME)];
memcpy(&hostname[0], CYW43_HOST_NAME, sizeof(CYW43_HOST_NAME));
netif_set_hostname(&cyw43_state.netif[CYW43_ITF_STA], hostname);
// initialize lwIP httpd stack
httpd_init();
// register cgi and ssi handlers with lwIP
http_set_cgi_handlers(httpd_cgi_paths, LWIP_ARRAYSIZE(httpd_cgi_paths));
http_set_ssi_handler(httpd_ssi_handler, httpd_ssi_tags, LWIP_ARRAYSIZE(httpd_ssi_tags));
}
#endif /* ENABLE_HTTPD */
================================================
FILE: hardware/rp2xxx/hw_registers.c
================================================
/******************************************************************************
* @file hw_registers.c
*
* @brief Functions for interacting directly with MCU hardware registers. The
* implementation of these functions is MCU-specific and will need to be
* changed if ported to a new hardware family.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 01-15-2025
*
* @copyright Copyright (c) 2025 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include "hardware_config.h"
uint32_t read_chip_register(uint32_t reg_addr) {
return *(volatile uint32_t *)reg_addr;
}
================================================
FILE: hardware/rp2xxx/hw_reset.c
================================================
/******************************************************************************
* @file hw_reset.c
*
* @brief Functions related to resetting the chip. The implementation of these
* functions is MCU-specific and will need to be changed if ported to a
* new hardware family.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include
#include "hardware_config.h"
#include "hardware/watchdog.h"
#include "pico/bootrom.h"
#ifdef USING_RP2040
#include "hardware/regs/vreg_and_chip_reset.h"
#elif USING_RP2350
#include "hardware/structs/powman.h"
#include "hardware/regs/powman.h"
#endif
reset_reason_t get_reset_reason(void) {
reset_reason_t reset_reason;
#ifdef USING_RP2040
// RP2040 CHIP_RESET register is stored in LDO_POR block -
// it can tell us POR, RUN pin, or debugger reset
io_rw_32 *chip_reset_reg = (io_rw_32 *)(VREG_AND_CHIP_RESET_BASE + VREG_AND_CHIP_RESET_CHIP_RESET_OFFSET);
// first check if it was a watchdog reboot before interrogating the reset register
if (watchdog_caused_reboot()) {
if (watchdog_enable_caused_reboot()) {
reset_reason = WATCHDOG; // expiration of watchdog timer
}
else reset_reason = FORCED; // program-forced watchdog reboot
}
else if (*chip_reset_reg & VREG_AND_CHIP_RESET_CHIP_RESET_HAD_PSM_RESTART_BITS) {
reset_reason = DEBUGGER; // reset from debugger -- currently this is not detecting correctly
}
else if (*chip_reset_reg & VREG_AND_CHIP_RESET_CHIP_RESET_HAD_POR_BITS) {
reset_reason = POWERON; // power-on or brownout
}
else if (*chip_reset_reg & VREG_AND_CHIP_RESET_CHIP_RESET_HAD_RUN_BITS) {
reset_reason = PIN; // reset pin ("run" pin) toggled
}
else reset_reason = UNKNOWN; // can't determine reset reason
#elif USING_RP2350
// RP2350 CHIP_RESET register is stored in POWMAN block -
// it can tell us POR, RUN pin, or debugger reset, plus glitch & brownout
powman_hw_t *powman_reg = powman_hw;
// first check if it was a watchdog reboot before interrogating the reset register
if (watchdog_caused_reboot()) {
if (watchdog_enable_caused_reboot()) {
reset_reason = WATCHDOG; // expiration of watchdog timer
}
else reset_reason = FORCED; // program-forced watchdog reboot
}
else if (powman_reg->chip_reset & POWMAN_CHIP_RESET_HAD_DP_RESET_REQ_BITS) {
reset_reason = DEBUGGER; // reset from ARM debugger
}
else if (powman_reg->chip_reset & POWMAN_CHIP_RESET_HAD_POR_BITS) {
reset_reason = POWERON; // normal power-on
}
else if (powman_reg->chip_reset & POWMAN_CHIP_RESET_HAD_GLITCH_DETECT_BITS) {
reset_reason = GLITCH; // reset from power glitch detect
}
else if (powman_reg->chip_reset & POWMAN_CHIP_RESET_HAD_BOR_BITS) {
reset_reason = BROWNOUT; // reset from brown-out detect
}
else if (powman_reg->chip_reset & POWMAN_CHIP_RESET_HAD_RUN_LOW_BITS) {
reset_reason = PIN; // reset pin ("run" pin) toggled
}
else if (powman_reg->chip_reset & POWMAN_CHIP_RESET_DOUBLE_TAP_BITS) {
reset_reason = DOUBLETAP; // reset pin ("run" pin) double-tapped
}
else reset_reason = UNKNOWN; // can't determine reset reason
#else
reset_reason = UNKNOWN;
#endif
return reset_reason;
}
char* get_reset_reason_string(reset_reason_t reset_reason) {
static char reset_reason_string[50];
strcpy(reset_reason_string, "Last reset reason: ");
switch (reset_reason)
{
case POWERON:
strcpy(reset_reason_string + strlen(reset_reason_string), "power-on\r\n");
break;
case GLITCH:
strcpy(reset_reason_string + strlen(reset_reason_string), "power supply glitch\r\n");
break;
case BROWNOUT:
strcpy(reset_reason_string + strlen(reset_reason_string), "power supply brown-out\r\n");
break;
case WATCHDOG:
strcpy(reset_reason_string + strlen(reset_reason_string), "watchdog\r\n");
break;
case FORCED:
strcpy(reset_reason_string + strlen(reset_reason_string), "program-requested\r\n");
break;
case PIN:
strcpy(reset_reason_string + strlen(reset_reason_string), "reset pin asserted\r\n");
break;
case DOUBLETAP:
strcpy(reset_reason_string + strlen(reset_reason_string), "reset pin double-tap\r\n");
break;
case DEBUGGER:
strcpy(reset_reason_string + strlen(reset_reason_string), "debugger\r\n");
break;
case UNKNOWN:
strcpy(reset_reason_string + strlen(reset_reason_string), "unknown\r\n");
break;
default:
break;
}
return reset_reason_string;
}
void reset_to_bootloader(void) {
// reset to the USB bootloader (i.e. BOOTSEL for UF2 mode)
reset_usb_boot(0, 0);
}
================================================
FILE: hardware/rp2xxx/hw_spi.c
================================================
/******************************************************************************
* @file hw_spi.c
*
* @brief Functions for configuring and interacting with the SPI peripherals.
* The implementation of these functions is MCU-specific and will need
* to be changed if ported to a new hardware family.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include "hardware_config.h"
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "hardware/spi.h"
#include "semphr.h"
// global SPI0 mutex
SemaphoreHandle_t spi0_mutex;
// chip select assertion routine to use in spi reads/writes
static inline void cs_assert(uint8_t cs_pin) {
asm volatile("nop \n nop \n nop");
gpio_put(cs_pin, 0);
asm volatile("nop \n nop \n nop");
}
// chip select de-assertion routine to use in spi reads/writes
static inline void cs_deassert(uint8_t cs_pin) {
asm volatile("nop \n nop \n nop");
gpio_put(cs_pin, 1);
asm volatile("nop \n nop \n nop");
}
void spi0_init(void) {
// create SPI0 mutex
spi0_mutex = xSemaphoreCreateMutex();
// initialize SPI0 and set pins
spi_init(SPI0_ID, SPI0_FREQ_KHZ * 1000);
gpio_set_function(SPI0_MISO_PIN, GPIO_FUNC_SPI);
gpio_set_function(SPI0_MOSI_PIN, GPIO_FUNC_SPI);
gpio_set_function(SPI0_CLK_PIN, GPIO_FUNC_SPI);
// initialize chip select and drive high (it is active low)
// for multiple devices on the bus, more chip selects would need to be added
gpio_init(SPI0_CS_PIN_DEFAULT);
gpio_set_dir(SPI0_CS_PIN_DEFAULT, GPIO_OUT);
gpio_put(SPI0_CS_PIN_DEFAULT, 1);
}
int spi0_write_register(uint8_t cs_pin, uint8_t reg_addr, uint8_t data_byte) {
int bytes_written;
uint8_t write_buf[2];
write_buf[0] = reg_addr & 0x7F; // write bit mask (MSB signifies read)
write_buf[1] = data_byte;
// obtain mutex on hw and then write to spi
if(xSemaphoreTake(spi0_mutex, 10) == pdTRUE) {
cs_assert(cs_pin);
bytes_written = spi_write_blocking(SPI0_ID, write_buf, 2);
cs_deassert(cs_pin);
xSemaphoreGive(spi0_mutex);
}
if (bytes_written == 2) {
return 1;
}
else return 0;
}
int spi0_read_registers(uint8_t cs_pin, uint8_t reg_addr, uint8_t *read_buf, uint16_t len) {
int bytes_read;
reg_addr |= 0x80; // make sure read bit is set (MSB)
// obtain mutex on hw and then read from spi
if(xSemaphoreTake(spi0_mutex, 10) == pdTRUE) {
cs_assert(cs_pin);
spi_write_blocking(SPI0_ID, ®_addr, 1); // write the address to read
wait_here_us(10 * 1000); // wait 10ms before read
bytes_read = spi_read_blocking(SPI0_ID, 0, read_buf, len); // read the result
cs_deassert(cs_pin);
xSemaphoreGive(spi0_mutex);
}
return bytes_read;
}
================================================
FILE: hardware/rp2xxx/hw_uart.c
================================================
/******************************************************************************
* @file hw_uart.c
*
* @brief Functions for configuring and accessing the UART peripheral used for
* the Command Line Interface (CLI), or auxiliary UART for other uses.
* The implementation of these functions is MCU-specific and will need
* to be changed if ported to a new hardware family.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include "hardware_config.h"
#include "shell.h"
#include "pico/stdlib.h"
#include "hardware/uart.h"
#include "hardware/irq.h"
#include "semphr.h"
#define NOCHAR 255 // return value of uart read if there is nothing to read
/************************
* CLI UART functions
*************************/
static char cli_uart_rx_char = NOCHAR;
// global CLI UART mutex
SemaphoreHandle_t cli_uart_mutex;
// cli uart rx interrupt handler
static void on_cli_uart_rx() {
if (uart_is_readable(UART_ID_CLI)) {
cli_uart_rx_char = uart_getc(UART_ID_CLI);
}
}
void cli_uart_init(void) {
// The CLI UART is intended to be accessed character by character at low-ish
// speed (human typing), with reads/writes regularly serviced by the CLI
// task. As such it is configured without FIFO, and a simple rx interrupt
// routine that puts the rx char into a global variable. It is assumed that
// microshell will pull the byte before a new incoming byte overwrites it.
// create CLI UART mutex
cli_uart_mutex = xSemaphoreCreateMutex();
// initialize uart at defined speed
uart_init(UART_ID_CLI, UART_BAUD_RATE_CLI);
// set tx and rx pins to the appropriate gpio function
gpio_set_function(UART_TX_PIN_CLI, GPIO_FUNC_UART);
gpio_set_function(UART_RX_PIN_CLI, GPIO_FUNC_UART);
// disable flow control
uart_set_hw_flow(UART_ID_CLI, false, false);
// set data format
uart_set_format(UART_ID_CLI, UART_DATA_BITS_CLI, UART_STOP_BITS_CLI, UART_PARITY_CLI);
// disable fifos - handle single characters only
uart_set_fifo_enabled(UART_ID_CLI, false);
// set up RX interrupt
int UART_IRQ = UART_ID_CLI == uart0 ? UART0_IRQ : UART1_IRQ;
irq_set_exclusive_handler(UART_IRQ, on_cli_uart_rx);
irq_set_enabled(UART_IRQ, true);
uart_set_irq_enables(UART_ID_CLI, true, false);
cli_uart_rx_char = NOCHAR; // clear out RX buffer as a junk char appears upon enable
// print out a string to indicate that uart was successfully initialized
uart_puts(UART_ID_CLI, "\r\n\n");
uart_puts(UART_ID_CLI, timestamp());
uart_puts(UART_ID_CLI, "CLI UART initialized\r\n");
}
int cli_uart_putc(char tx_char) {
int status = 0;
if (uart_is_writable(UART_ID_CLI)) {
if(xSemaphoreTake(cli_uart_mutex, 10) == pdTRUE) {
// if uart is writeable and semaphore is obtained, write the char
uart_putc_raw(UART_ID_CLI, tx_char);
xSemaphoreGive(cli_uart_mutex);
status = 1;
}
}
return status;
}
char cli_uart_getc(void) {
char ch;
if (cli_uart_rx_char != NOCHAR) {
// try to obtain the mutex to allow reading the buffered single char.
// note that the direct UART read is inherently thread safe because it is
// accessed thru a static ISR (on_cli_uart_rx).
// of course this does not prevent another incoming UART char from overwriting
// the buffered char if it is not pulled out in time.
if(xSemaphoreTake(cli_uart_mutex, 10) == pdTRUE) {
ch = cli_uart_rx_char;
cli_uart_rx_char = NOCHAR;
xSemaphoreGive(cli_uart_mutex);
}
return ch;
}
}
void cli_uart_puts(const char *print_string) {
if(xSemaphoreTake(cli_uart_mutex, 10) == pdTRUE) {
uart_puts(UART_ID_CLI, print_string);
xSemaphoreGive(cli_uart_mutex);
}
}
/************************
* AUX UART functions
*************************/
// global Aux UART mutex
SemaphoreHandle_t aux_uart_mutex;
void aux_uart_init(void) {
// The auxilliary uart is a multi-purpose serial interface intended to send
// and receive multiple bytes at a time, without any specific servicing
// routine implemented. The FIFO is enabled but there is no RX ISR; on
// the RP2040 platform this means we have 32 bytes of hw buffer before data
// starts getting overwritten by any more incoming bytes.
// Refer to cli_uart_init() if the need to implement an RX ISR arises.
// create Aux UART mutex
aux_uart_mutex = xSemaphoreCreateMutex();
// initialize uart at defined speed
uart_init(UART_ID_AUX, UART_BAUD_RATE_AUX);
// set tx and rx pins to the appropriate gpio function
gpio_set_function(UART_TX_PIN_AUX, GPIO_FUNC_UART);
gpio_set_function(UART_RX_PIN_AUX, GPIO_FUNC_UART);
// disable flow control
uart_set_hw_flow(UART_ID_AUX, false, false);
// set data format
uart_set_format(UART_ID_AUX, UART_DATA_BITS_AUX, UART_STOP_BITS_AUX, UART_PARITY_AUX);
// enable fifos
uart_set_fifo_enabled(UART_ID_AUX, true);
}
int aux_uart_write(uint8_t *tx_data, size_t tx_len) {
int status = 0;
if (uart_is_writable(UART_ID_AUX)) {
if(xSemaphoreTake(aux_uart_mutex, 10) == pdTRUE) {
// if uart is writeable and semaphore is obtained, write the data
uart_write_blocking(UART_ID_AUX, tx_data, tx_len);
xSemaphoreGive(aux_uart_mutex);
status = 1;
}
}
return status;
}
int aux_uart_read(uint8_t *rx_data, size_t rx_len) {
int rx_pos = 0;
// read from uart if there is data in the fifo, up to rx_len times
while(uart_is_readable(UART_ID_AUX) && rx_pos < rx_len) {
// first try to obtain mutex
if(xSemaphoreTake(aux_uart_mutex, 10) == pdTRUE) {
rx_data[rx_pos] = (uint8_t) uart_getc(UART_ID_AUX);
xSemaphoreGive(aux_uart_mutex);
rx_pos++;
}
}
return rx_pos + 1;
}
================================================
FILE: hardware/rp2xxx/hw_usb.c
================================================
/******************************************************************************
* @file hw_usb.c
*
* @brief Functions for configuring and implementing a USB device using the
* TinyUSB library. The implementation of these functions may be
* MCU-specific and will need to be updated if ported to a new hardware
* family.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include "hardware_config.h"
#include "pico/stdlib.h"
#include "hardware/flash.h"
#include "tusb.h"
#include "semphr.h"
// global USB mutex
SemaphoreHandle_t usb_mutex;
/************************
* USB Descriptor Setup
*************************/
#define DESC_STR_MAX 20
#define USBD_VID 0x2E8A // Vendor: Raspberry Pi
#define USBD_PID 0x000A // Product: Raspberry Pi Pico CDC
#define USBD_DESC_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN * CFG_TUD_CDC)
#define USBD_MAX_POWER_MA 500
#define USBD_ITF_CDC_0 0
#define USBD_ITF_MAX 2
#define USBD_CDC_0_EP_CMD 0x81
#define USBD_CDC_0_EP_OUT 0x01
#define USBD_CDC_0_EP_IN 0x82
#define USBD_CDC_CMD_MAX_SIZE 8
#define USBD_CDC_IN_OUT_MAX_SIZE 64
#define USBD_STR_0 0x00
#define USBD_STR_MANUF 0x01
#define USBD_STR_PRODUCT 0x02
#define USBD_STR_SERIAL 0x03
#define USBD_STR_SERIAL_LEN 17
#define USBD_STR_CDC 0x04
static const tusb_desc_device_t usbd_desc_device = {
.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = USBD_VID,
.idProduct = USBD_PID,
.bcdDevice = 0x0100,
.iManufacturer = USBD_STR_MANUF,
.iProduct = USBD_STR_PRODUCT,
.iSerialNumber = USBD_STR_SERIAL,
.bNumConfigurations = 1,
};
// endpoint descriptors, can add additional TUD_CDC_DESCRIPTOR to array for more
// endpoints in the composite device
static const uint8_t usbd_desc_cfg[USBD_DESC_LEN] = {
TUD_CONFIG_DESCRIPTOR(1, USBD_ITF_MAX, USBD_STR_0, USBD_DESC_LEN,
TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, USBD_MAX_POWER_MA),
TUD_CDC_DESCRIPTOR(USBD_ITF_CDC_0, USBD_STR_CDC, USBD_CDC_0_EP_CMD,
USBD_CDC_CMD_MAX_SIZE, USBD_CDC_0_EP_OUT, USBD_CDC_0_EP_IN,
USBD_CDC_IN_OUT_MAX_SIZE),
};
static char usb_serialno[USBD_STR_SERIAL_LEN] = "000000000000";
static const char *const usbd_desc_str[] = {
[USBD_STR_MANUF] = "Raspberry Pi",
[USBD_STR_PRODUCT] = "Pico",
[USBD_STR_SERIAL] = usb_serialno,
[USBD_STR_CDC] = "Board CDC",
};
/*******************************
* TinyUSB Descriptor Callbacks
********************************/
// TinyUSB callback to provide the device profile
const uint8_t *tud_descriptor_device_cb(void) {
return (const uint8_t *) &usbd_desc_device;
}
// TinyUSB callback to provide configuration, interface, and endpoint profiles
const uint8_t *tud_descriptor_configuration_cb(uint8_t index) {
return usbd_desc_cfg;
}
// TinyUSB callback to provide the device strings in UTF8
const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
static uint16_t desc_str[DESC_STR_MAX];
uint8_t len;
if (index == 0) {
desc_str[1] = 0x0409;
len = 1;
} else {
const char *str;
char serial[USBD_STR_SERIAL_LEN];
if (index >= sizeof(usbd_desc_str) / sizeof(usbd_desc_str[0]))
return NULL;
str = usbd_desc_str[index];
for (len = 0; len < DESC_STR_MAX - 1 && str[len]; ++len)
desc_str[1 + len] = str[len];
}
desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * len + 2);
return desc_str;
}
void usb_serialno_init(void) {
uint8_t id[8];
// Get unique board ID from the Pico flash (programmed during manufacturing)
flash_get_unique_id(id);
snprintf(usb_serialno,
USBD_STR_SERIAL_LEN,
"%02X%02X%02X%02X%02X%02X%02X%02X",
id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7]);
}
void usb_device_init(void) {
// create USB mutex
usb_mutex = xSemaphoreCreateMutex();
// generate unique USB device serial no. from RPi Pico unique flash ID
usb_serialno_init();
// initialize the TinyUSB device
tusb_init();
}
void usb_read_bytes(struct usb_iface_t *usb_iface) {
// check if connected
if (usb_iface->is_conn) {
// check if bytes available to read
uint16_t len = tud_cdc_n_available(usb_iface->iface_id);
// read bytes if available and less than size left in buffer
if (len > 0 && (usb_iface->rx_pos + len) < CFG_TUD_CDC_RX_BUFSIZE) {
if(xSemaphoreTake(usb_mutex, 10) == pdTRUE) { // attempt to acquire mutex so we can read
usb_iface->rx_pos += tud_cdc_n_read(usb_iface->iface_id, usb_iface->rx_buffer + usb_iface->rx_pos, len);
xSemaphoreGive(usb_mutex);
}
}
}
}
void usb_write_bytes(struct usb_iface_t *usb_iface) {
// check if connected and there are bytes to send
if (usb_iface->is_conn && usb_iface->tx_pos > 0) {
if(xSemaphoreTake(usb_mutex, 10) == pdTRUE) { // attempt to acquire mutex so we can write
uint16_t count = tud_cdc_n_write(usb_iface->iface_id, usb_iface->tx_buffer, usb_iface->tx_pos);
if (count > 0) {
tud_cdc_n_write_flush(usb_iface->iface_id);
if (count < usb_iface->tx_pos) { // if all bytes in the write buffer haven't been written, shift them up
memmove(usb_iface->tx_buffer, &usb_iface->tx_buffer[count], usb_iface->tx_pos - count);
usb_iface->tx_pos -= count;
}
else {
usb_iface->tx_pos = 0;
}
}
xSemaphoreGive(usb_mutex);
}
}
}
int cli_usb_putc(char tx_char) {
int status = 0;
// check if connected
if (tud_cdc_n_connected(CDC_ID_CLI)) {
// write single char (byte) if mutex is available
if(xSemaphoreTake(usb_mutex, 10) == pdTRUE) {
if (tud_cdc_n_write_char(CDC_ID_CLI, tx_char) == 1) {
tud_cdc_n_write_flush(CDC_ID_CLI);
status = 1;
}
xSemaphoreGive(usb_mutex);
}
}
return status;
}
char cli_usb_getc(void) {
char readchar = NOCHAR;
// check if connected and there are bytes available
if (tud_cdc_n_connected(CDC_ID_CLI) && tud_cdc_n_available(CDC_ID_CLI) > 0) {
// read single char (byte) if mutex is available
if(xSemaphoreTake(usb_mutex, 10) == pdTRUE) {
readchar = tud_cdc_n_read_char(CDC_ID_CLI);
xSemaphoreGive(usb_mutex);
}
}
return readchar;
}
================================================
FILE: hardware/rp2xxx/hw_versions.c
================================================
/******************************************************************************
* @file hw_versions.c
*
* @brief Functions to query MCU version numbers. The implementation of these
* functions is MCU-specific and will need to be changed if ported to a
* new hardware family.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include "hardware_config.h"
#include "pico/platform.h"
uint8_t get_chip_version(void){
#ifdef USING_RP2350
return rp2350_chip_version();
#else
return rp2040_chip_version();
#endif
}
uint8_t get_rom_version(void){
return rp2040_rom_version();
// there currently is no rp2350_rom_version()
}
================================================
FILE: hardware/rp2xxx/hw_watchdog.c
================================================
/******************************************************************************
* @file hw_watchdog.c
*
* @brief Functions to interact with the watchdog hardware peripheral. The
* implementation of these functions is MCU-specific and will need to be
* changed if ported to a new hardware family.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include "hardware_config.h"
#include "hardware/watchdog.h"
#include "hardware/structs/watchdog.h"
void watchdog_en(uint32_t delay_ms) {
watchdog_enable(delay_ms, 1); // pause_on_debug true, should allow us to debug normally
}
void watchdog_dis(void) {
watchdog_disable();
}
void watchdog_kick(void) {
watchdog_update(); // reset the watchdog timer
}
void force_watchdog_reboot(void) {
// force watchdog reboot
watchdog_reboot(0, 0, WATCHDOG_DELAY_REBOOT_MS);
// loop until watchdog reboot
while(1);
}
================================================
FILE: hardware/rp2xxx/hw_wifi.c
================================================
/******************************************************************************
* @file hw_wifi.c
*
* @brief Function implementations for WiFi connectivity using the CYW43
* wireless module. The implementation of these functions is hardware and
* SDK-specific and will need to be changed if ported to a new device.
*
* @author Alec Lanter (Github @Kintar)
* @author Cavin McKinley (Github @mcknly)
*
* @date 01-24-2025
*
* @copyright Copyright (c) 2025 Alec Lanter, Cavin McKinley
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include "hw_wifi.h"
#include "pico/cyw43_arch.h"
#include "pico/stdlib.h"
#include "hardware/gpio.h"
static hw_wifi_mode_t current_mode = HW_WIFI_MODE_NONE;
void hw_wifi_hard_reset(void) {
// toggle CYW43 WL_ON GPIO to make sure we are starting from POR
gpio_init(23);
gpio_set_dir(23, GPIO_OUT);
gpio_put(23, 0); // set WL_ON low to hold in reset
busy_wait_us(1000);
gpio_put(23,1); // release reset
// note: per the CYW43 data sheet, "wait at least 150ms after VDDC and
// VDDIO are available before initiating SDIO access". Should we have
// a hard pause here?
}
bool hw_wifi_is_initialized() {
return cyw43_is_initialized(&cyw43_state);
}
bool hw_wifi_init() {
if (hw_wifi_is_initialized()) {
return true;
}
else {
// force POR on wifi module if we are unsure of state
// hw_wifi_hard_reset();
// initialize the wifi module
return !cyw43_arch_init();
}
}
bool hw_wifi_init_with_country(hw_wifi_country_t country_code) {
if (hw_wifi_is_initialized()) {
return true;
}
else {
// force POR on wifi module if we are unsure of state
// hw_wifi_hard_reset();
// initialize the wifi module
return !cyw43_arch_init_with_country(country_code);
}
}
void hw_wifi_deinit() {
if (hw_wifi_is_initialized()) return;
cyw43_arch_deinit();
}
static uint32_t hw_wifi_auth_to_cyw43(hw_wifi_auth_t auth) {
switch (auth) {
case HW_WIFI_AUTH_MIXED:
return CYW43_AUTH_WPA2_MIXED_PSK;
case HW_WIFI_AUTH_WPA_TPIK_PSK:
return CYW43_AUTH_WPA_TKIP_PSK;
case HW_WIFI_AUTH_WPA2_AES_PSK:
return CYW43_AUTH_WPA2_AES_PSK;
default:
return CYW43_AUTH_OPEN;
}
}
void hw_wifi_enable_sta_mode() {
hw_wifi_disable_ap_mode();
cyw43_arch_enable_sta_mode();
current_mode = HW_WIFI_MODE_STA;
}
void hw_wifi_disable_sta_mode() {
if (current_mode == HW_WIFI_MODE_STA) {
cyw43_arch_disable_sta_mode();
current_mode = HW_WIFI_MODE_NONE;
}
}
void hw_wifi_enable_ap_mode(const char *ssid, const char *password, hw_wifi_auth_t auth_type) {
hw_wifi_disable_sta_mode();
cyw43_arch_enable_ap_mode(ssid, password, hw_wifi_auth_to_cyw43(auth_type));
current_mode = HW_WIFI_MODE_AP;
}
void hw_wifi_disable_ap_mode() {
if (current_mode == HW_WIFI_MODE_AP) {
cyw43_arch_disable_ap_mode();
current_mode = HW_WIFI_MODE_NONE;
}
}
bool hw_wifi_connect(const char *ssid, const char *password, hw_wifi_auth_t auth_type) {
uint32_t cw_auth = hw_wifi_auth_to_cyw43(auth_type);
return !cyw43_arch_wifi_connect_blocking(ssid, password, cw_auth);
}
bool hw_wifi_connect_async(const char *ssid, const char *password, hw_wifi_auth_t auth_type) {
uint32_t cw_auth = hw_wifi_auth_to_cyw43(auth_type);
return !cyw43_arch_wifi_connect_async(ssid, password, cw_auth);
}
void hw_wifi_reset_connection(void) {
// disable and re-enable AP mode to reset connection
hw_wifi_disable_ap_mode();
hw_wifi_disable_sta_mode();
cyw43_arch_enable_sta_mode();
current_mode = HW_WIFI_MODE_STA;
}
const ip_addr_t *hw_wifi_get_addr() {
// query lwIP for address
return netif_ip4_addr(netif_list);
}
hw_wifi_status_t hw_wifi_get_status() {
// AP mode always returns LINKDOWN from cyw43
if (current_mode == HW_WIFI_MODE_AP) {
return HW_WIFI_STATUS_LINK_DOWN;
}
// TODO: the cyw43_arch_wifi_link_status function will not actually return
// all the statuses below. In fact, it appears that it always returns
// CYW43_LINK_JOIN. This function is somewhat useless for now, it may be
// that the pico-sdk implementation is flawed, needs more research.
uint32_t status = cyw43_wifi_link_status(&cyw43_state, CYW43_ITF_STA);
switch (status) {
case CYW43_LINK_DOWN:
return HW_WIFI_STATUS_LINK_DOWN;
case CYW43_LINK_JOIN:
return HW_WIFI_STATUS_JOINED;
case CYW43_LINK_NOIP:
return HW_WIFI_STATUS_NOIP;
case CYW43_LINK_UP:
return HW_WIFI_STATUS_UP;
case CYW43_LINK_FAIL:
return HW_WIFI_STATUS_FAIL;
case CYW43_LINK_NONET:
return HW_WIFI_STATUS_NONET;
case CYW43_LINK_BADAUTH:
return HW_WIFI_STATUS_BADAUTH;
default:
return HW_WIFI_STATUS_UNKNOWN;
}
}
================================================
FILE: hardware/rp2xxx/net_inc/httpd_content/404.html
================================================
BBOS
BreadboardOS
404 - Page not found
...:::-------::::...
.:--=+*##%%%%%%%%%%%%###########%%%%###*++=-:.
.:=+*#%%@%%##*+=--:. .....::::::.......::--=++**##**+-:.
:-+#%@@%%#+=-: ..::-==+++***###%%%%%%%###***++=======++***+=:
:+%@@%#+=:. .:-=+##%@@@@@@@@@@%%%###****+++++++++**###%%@@@@@%%####%%#+:
.+@@%*-..::. :-+#%@@@@@@%#**+=--::.. ..:-=+*#%@@@@@%=.
.*@@+: -=-..-+%@@@@%*+=--::.. ....:::::-----:::.. .:=+#@@@#:
=@@+... :=:.:+@@@%*=::::--::::::::------:::.. ..:-----::.. ...-#@@#:
*@%: ::-=: .*@@%+:..:--::..:-----:. ..:::::. .::: .*@@+
#@#. ::-: +@@#-:..::. .:::::. .. ::-: -@@*
+@% :- .#@@=:. .--:. .=.:= -@@=
:@@: .: .#@%-:. . :: - :- +@@.
*@+ . .=@@-:. -. .. . . @@=
.@@. .:#@*.. =. *@*
-@% :-@@-. -: =@%
+@* :-@%: = . -@%
+@+ :-@%: .: -. :.:@@
=@* . .-@%. . -: = -@%
.@% - -::.:%@- -. + =@#
*@= - =::.+@# -+ :- : :: *@=
+@+:=-+. :#@* .-+@@* =@@#=: . @@
.*@+=+%*--#@%: =%%%@%#=. -+#%@%%#. #@-
*@:..:=++--*@= .-::. .:: =%%-
@#. ..:@@-. . . :@@:
*@: - .:%@+. : :+**=. .*@@@%+ : +@@:
-@= .: .:*@#: : *@@@@@@+ %@@@@@@# :. %@%
@#. .=@%: :: . =@@@@@@@@- :@@@@@@@@: - :@@*
*@-- .:.-@@= .- :: #@@@@@@@@= .@@@@@@@@. - +@@-
:@+=. ..:%@+. = .= =@@@@@@@@. =@@@@@%- :: %@@
%%=: : : .*@#: =. = =@@@@@#: .-==: = .@@#
=@=- -.:. .=@@: -: - .-=-. . . + =@@=
.@#: .-.- .-@@= .- : -. . . -- . -: .: :- #@@:
#@. = = .:#@+. - :: . :: -=++++- . . :. =. @@%
-@= : =. :*@#. : -+-. .-+: = + :@@*
.@% := :=@@: -: : .= -= +@@-
#@. + :-@@= =: +. #@@.
=@= = .:%@+. + .+ .@@#
:@# :..:*@#. :- =- -@@+
@@ .. :+@%: : - + +@@-
#@. :-@@- - - : := %@@
+@- ::@@= = = =. .@@#
=@+ .:%@+ + + = =@@=
:@# .:*@#. + = :: #@@.
@@ .+@%. + - - .@@#
#@: .=@%: = .. +@@-
=@+ .-@@: -. . .@@#
@@ .-@@- .. .::..... .%@@:
..:::---+@# ::%@= .:::. ... .....-+%@@#++==---:::..
.:--==--====+++#@%=: .+@%: .::::::. ......:::::--====+++****#%@@@@@@@@@@@%+=======---:.
=====-=+*#%%@@@@@@@@@%#+=-#@%-. ..... ..:::--===++***##%%%%@@@@@@@@@@@@@@@@@@@@@@@@@%#**+================.
:--===*@@@@@@@@@@@@@@@@@@@@@@@%##******####%%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%##*++==-----===========--:.
.:::-=+*#%%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%######******+++++++========---==========----:::..
..::---==++++******#####********+++++========----------------------------::::....
....................................
Sorry, the slice you are requesting was not baked in this server.
================================================
FILE: hardware/rp2xxx/net_inc/httpd_content/index.shtml
================================================
BBOS
[[ Welcome to BreadboardOS! ]]
Platform:
Uptime:
Free RAM:
Free flash:
CGI tests:
led-on
led-off
404 test: /nonexistent
================================================
FILE: hardware/rp2xxx/net_inc/httpd_content/test.shtml
================================================
BBOS
cgi test
The onboard LED should be [
]!
This test demonstrates how to use CGI key/value pairs
to pass information to the http server, and provide status
feedback via SSI tags. Note the URI including led-state query.
Go back
================================================
FILE: hardware/rp2xxx/net_inc/hw_net.h
================================================
/******************************************************************************
* @file hw_net.h
*
* @brief Settings, definitions, and function prototypes for various network
* stacks, much of it tied to lwIP. Hardware and SDK-specific
* implementation resides within these functions.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-06-2025
*
* @copyright Copyright (c) 2025 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#ifndef HW_NET_H
#define HW_NET_H
#include
#include "lwip/arch.h"
/**
* @brief Initialize mDNS
*
* This function intializes the lwIP mDNS stack. Once running, the device will
* be discoverable on the local network via the assigned hostname (set by the
* HOSTNAME plus BOARD variables in project.cmake). The resulting mDNS host will
* be accessible at '-.local'.
*
* @param none
*
* @return nothing
*/
void net_mdns_init(void);
/**
* @brief Initialize the httpd stack
*
* This function is called to initialize the lwIP httpd (web server) stack. Once
* running, the web server is available on the standard http port (80) and can
* serve dynamic content via SSI. Must already be connected to a network and have
* an assigned IP.
*
* @param none
*
* @return nothing
*/
void net_httpd_stack_init(void);
/**
* @brief lwIP httpd CGI handler
*
* This function is called by the lwIP httpd stack when a CGI request is made
* (i.e. the key/value pair in the URI after the '?' character). This function
* must be assigned to a path in the httpd_cgi_paths array. Note that multiple
* CGI handler functions can be defined and assigned to different paths.
*
* @param tCGIHandler Params given by lwIP typedef tCGIHandler. See lwip/apps/httpd.h
*
* @return pointer to URI path string returned to the browser
*/
static const char *httpd_cgi_handler(int iIndex, int iNumParams, char *pcParam[], char *pcValue[]);
/**
* @brief lwIP httpd SSI handler
*
* This function is called by the lwIP httpd stack when an SSI request is made
* (i.e. the server found a SSI tag in the html file). This function contains
* logic which matches the iIndex tag index number defined by the httpd_ssi_tags
* array. The function will return the string to be inserted into the HTML file
* in place of the tag. Note that there is only one SSI handler function assigned
* to the lwIP httpd stack.
*
* @param tSSIHandler Params given by lwIP typedef tSSIHandler. See lwip/apps/httpd.h
*
* @return number of characters written in place of the SSI tag
*/
u16_t httpd_ssi_handler(int iIndex, char *pcInsert, int iInsertLen, uint16_t current_tag_part, uint16_t *next_tag_part);
#endif /* HW_NET_H */
================================================
FILE: hardware/rp2xxx/net_inc/hw_wifi.h
================================================
/******************************************************************************
* @file hw_wifi.h
*
* @brief settings, definitions, typedefs and function prototypes for WiFi and
* interacting with the CYW43 wireless module. Hardware and SDK-specific
* implementation resides within these functions.
*
* @author Alec Lanter (Github @Kintar)
* @author Cavin McKinley (Github @mcknly)
*
* @date 01-24-2025
*
* @copyright Copyright (c) 2025 Alec Lanter, Cavin McKinley
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#ifndef HW_WIFI_H
#define HW_WIFI_H
#include
#include
#include "pico/cyw43_arch.h"
typedef uint32_t hw_wifi_country_t ;
// create a country code from the two character country and revision number
#define HW_WIFI_COUNTRY(A, B, REV) (hw_wifi_country_t)((unsigned char)(A) | ((unsigned char)(B) << 8) | ((REV) << 16))
// Worldwide Locale (passive Ch12-14)
#define HW_WIFI_COUNTRY_WORLDWIDE HW_WIFI_COUNTRY('X', 'X', 0)
#define HW_WIFI_COUNTRY_AUSTRALIA HW_WIFI_COUNTRY('A', 'U', 0)
#define HW_WIFI_COUNTRY_AUSTRIA HW_WIFI_COUNTRY('A', 'T', 0)
#define HW_WIFI_COUNTRY_BELGIUM HW_WIFI_COUNTRY('B', 'E', 0)
#define HW_WIFI_COUNTRY_BRAZIL HW_WIFI_COUNTRY('B', 'R', 0)
#define HW_WIFI_COUNTRY_CANADA HW_WIFI_COUNTRY('C', 'A', 0)
#define HW_WIFI_COUNTRY_CHILE HW_WIFI_COUNTRY('C', 'L', 0)
#define HW_WIFI_COUNTRY_CHINA HW_WIFI_COUNTRY('C', 'N', 0)
#define HW_WIFI_COUNTRY_COLOMBIA HW_WIFI_COUNTRY('C', 'O', 0)
#define HW_WIFI_COUNTRY_CZECH_REPUBLIC HW_WIFI_COUNTRY('C', 'Z', 0)
#define HW_WIFI_COUNTRY_DENMARK HW_WIFI_COUNTRY('D', 'K', 0)
#define HW_WIFI_COUNTRY_ESTONIA HW_WIFI_COUNTRY('E', 'E', 0)
#define HW_WIFI_COUNTRY_FINLAND HW_WIFI_COUNTRY('F', 'I', 0)
#define HW_WIFI_COUNTRY_FRANCE HW_WIFI_COUNTRY('F', 'R', 0)
#define HW_WIFI_COUNTRY_GERMANY HW_WIFI_COUNTRY('D', 'E', 0)
#define HW_WIFI_COUNTRY_GREECE HW_WIFI_COUNTRY('G', 'R', 0)
#define HW_WIFI_COUNTRY_HONG_KONG HW_WIFI_COUNTRY('H', 'K', 0)
#define HW_WIFI_COUNTRY_HUNGARY HW_WIFI_COUNTRY('H', 'U', 0)
#define HW_WIFI_COUNTRY_ICELAND HW_WIFI_COUNTRY('I', 'S', 0)
#define HW_WIFI_COUNTRY_INDIA HW_WIFI_COUNTRY('I', 'N', 0)
#define HW_WIFI_COUNTRY_ISRAEL HW_WIFI_COUNTRY('I', 'L', 0)
#define HW_WIFI_COUNTRY_ITALY HW_WIFI_COUNTRY('I', 'T', 0)
#define HW_WIFI_COUNTRY_JAPAN HW_WIFI_COUNTRY('J', 'P', 0)
#define HW_WIFI_COUNTRY_KENYA HW_WIFI_COUNTRY('K', 'E', 0)
#define HW_WIFI_COUNTRY_LATVIA HW_WIFI_COUNTRY('L', 'V', 0)
#define HW_WIFI_COUNTRY_LIECHTENSTEIN HW_WIFI_COUNTRY('L', 'I', 0)
#define HW_WIFI_COUNTRY_LITHUANIA HW_WIFI_COUNTRY('L', 'T', 0)
#define HW_WIFI_COUNTRY_LUXEMBOURG HW_WIFI_COUNTRY('L', 'U', 0)
#define HW_WIFI_COUNTRY_MALAYSIA HW_WIFI_COUNTRY('M', 'Y', 0)
#define HW_WIFI_COUNTRY_MALTA HW_WIFI_COUNTRY('M', 'T', 0)
#define HW_WIFI_COUNTRY_MEXICO HW_WIFI_COUNTRY('M', 'X', 0)
#define HW_WIFI_COUNTRY_NETHERLANDS HW_WIFI_COUNTRY('N', 'L', 0)
#define HW_WIFI_COUNTRY_NEW_ZEALAND HW_WIFI_COUNTRY('N', 'Z', 0)
#define HW_WIFI_COUNTRY_NIGERIA HW_WIFI_COUNTRY('N', 'G', 0)
#define HW_WIFI_COUNTRY_NORWAY HW_WIFI_COUNTRY('N', 'O', 0)
#define HW_WIFI_COUNTRY_PERU HW_WIFI_COUNTRY('P', 'E', 0)
#define HW_WIFI_COUNTRY_PHILIPPINES HW_WIFI_COUNTRY('P', 'H', 0)
#define HW_WIFI_COUNTRY_POLAND HW_WIFI_COUNTRY('P', 'L', 0)
#define HW_WIFI_COUNTRY_PORTUGAL HW_WIFI_COUNTRY('P', 'T', 0)
#define HW_WIFI_COUNTRY_SINGAPORE HW_WIFI_COUNTRY('S', 'G', 0)
#define HW_WIFI_COUNTRY_SLOVAKIA HW_WIFI_COUNTRY('S', 'K', 0)
#define HW_WIFI_COUNTRY_SLOVENIA HW_WIFI_COUNTRY('S', 'I', 0)
#define HW_WIFI_COUNTRY_SOUTH_AFRICA HW_WIFI_COUNTRY('Z', 'A', 0)
#define HW_WIFI_COUNTRY_SOUTH_KOREA HW_WIFI_COUNTRY('K', 'R', 0)
#define HW_WIFI_COUNTRY_SPAIN HW_WIFI_COUNTRY('E', 'S', 0)
#define HW_WIFI_COUNTRY_SWEDEN HW_WIFI_COUNTRY('S', 'E', 0)
#define HW_WIFI_COUNTRY_SWITZERLAND HW_WIFI_COUNTRY('C', 'H', 0)
#define HW_WIFI_COUNTRY_TAIWAN HW_WIFI_COUNTRY('T', 'W', 0)
#define HW_WIFI_COUNTRY_THAILAND HW_WIFI_COUNTRY('T', 'H', 0)
#define HW_WIFI_COUNTRY_TURKEY HW_WIFI_COUNTRY('T', 'R', 0)
#define HW_WIFI_COUNTRY_UK HW_WIFI_COUNTRY('G', 'B', 0)
#define HW_WIFI_COUNTRY_USA HW_WIFI_COUNTRY('U', 'S', 0)
// wifi auth types
typedef enum{
HW_WIFI_AUTH_OPEN,
HW_WIFI_AUTH_WPA_TPIK_PSK,
HW_WIFI_AUTH_WPA2_AES_PSK,
HW_WIFI_AUTH_MIXED,
} hw_wifi_auth_t ;
// wifi modes
typedef enum{
HW_WIFI_MODE_NONE,
HW_WIFI_MODE_STA,
HW_WIFI_MODE_AP,
} hw_wifi_mode_t ;
// wifi status
typedef enum{
HW_WIFI_STATUS_LINK_DOWN,
HW_WIFI_STATUS_JOINED,
HW_WIFI_STATUS_NOIP,
HW_WIFI_STATUS_UP,
HW_WIFI_STATUS_FAIL,
HW_WIFI_STATUS_BADAUTH,
HW_WIFI_STATUS_NONET,
HW_WIFI_STATUS_UNKNOWN,
} hw_wifi_status_t ;
// IP address (IPv4)
typedef uint32_t hw_wifi_ip_addr_t;
/**************************************************
* Wireless module (CYW43) initialization functions
***************************************************/
/**
* @brief Perform a hard reset of the CYW43 module.
*
* Force a hard reset of the CYW43 module by toggling the reset pin with the
* appropriate RP2xxx GPIO pin.
*
* @param none
*
* @return nothing
*/
void hw_wifi_hard_reset(void);
/**
* @brief Check if the CYW43 module is initialized.
*
* Performs a check to determine if the CYW43 module has already been initialized
* so that it is not tried multiple times, with a boolean response to indicate.
*
* @param none
*
* @return true if the CYW43 module is already initialized, otherwise false
*/
bool hw_wifi_is_initialized(void);
/**
* @brief Initialize the CYW43 module without country code.
*
* Check weather the CYW43 module has already been initialized, and if not,
* perform the initialization routine. This routine will also initialze the lwIP
* stack. Note that this function does not use a country code, and will default
* to the worldwide locale. This may not give the best performance in all regions.
*
* @param none
*
* @return true if initialization was successful, otherwise false
*/
bool hw_wifi_init(void);
/**
* @brief Initialize the CYW43 module with country code.
*
* Check weather the CYW43 module has already been initialized, and if not,
* perform the initialization routine using the given country code. This routine
* will also initialze the lwIP stack.
*
* @param country_code country code from the list of definitions in this file
*
* @return true if initialization was successful, otherwise false
*/
bool hw_wifi_init_with_country(hw_wifi_country_t country_code);
/**
* @brief De-initialize the CYW43 module.
*
* This will de-initialize the CYW43 driver and the lwIP stack.
*
* @param none
*
* @return nothing
*/
void hw_wifi_deinit(void);
/*****************************
* WiFi mode control functions
******************************/
// NOTE: the pico_w will _technically_ support simultaneous AP and STA mode
// connections, but this implementation does not.
/**
* @brief Enable STA mode for the WiFi module.
*
* This function will enable the STA mode for the WiFi module, disabling AP mode
* if it is currently enabled.
*
* @param none
*
* @return nothing
*/
void hw_wifi_enable_sta_mode(void);
/**
* @brief Disable STA mode for the WiFi module.
*
* This function will disable the STA mode for the WiFi module.
*
* @param none
*
* @return nothing
*/
void hw_wifi_disable_sta_mode(void);
/**
* @brief Enable AP mode for the WiFi module.
*
* This function will enable the AP mode for the WiFi module, disabling STA mode
* if it is currently enabled.
*
* @param ssid SSID string for the AP to broadcast
* @param password password string for the AP
* @param auth_type authentication type for the AP
*
* @return nothing
*/
void hw_wifi_enable_ap_mode(const char *ssid, const char *password, hw_wifi_auth_t auth_type);
/**
* @brief Disable AP mode for the WiFi module.
*
* This function will disable the AP mode for the WiFi module.
*
* @param none
*
* @return nothing
*/
void hw_wifi_disable_ap_mode(void);
/**************************************
* WiFi connection and status functions
***************************************/
/**
* @brief Connect to a WiFi network (blocking).
*
* Connect to a WiFi network with the given SSID, password, and authentication
* type. This function will block until the connection is established.
*
* @param ssid SSID string of the network to connect to
* @param password password string for the network
* @param auth_type authentication type for the network
*
* @return true if the connection was successful, false if connection error detected
*/
bool hw_wifi_connect(const char *ssid, const char *password, hw_wifi_auth_t auth_type);
/**
* @brief Connect to a WiFi network (non-blocking).
*
* Connect to a WiFi network with the given SSID, password, and authentication
* type. This function can be called in a non-blocking fashion.
*
* @param ssid SSID string of the network to connect to
* @param password password string for the network
* @param auth_type authentication type for the network
*
* @return true if the connection was successful, false if connection error detected
*/
bool hw_wifi_connect_async(const char *ssid, const char *password, hw_wifi_auth_t auth_type);
/**
* @brief Reset the WiFi network connection.
*
* This function will disable and re-enable AP mode (using hw_wifi_disable_ap_mode
* and hw_wifi_enable_ap_mode) to effectively disconnect/reset the WiFi connection.
* It is essentially a helper function since there is no direct disconnect function.
*
* @param none
*
* @return nothing
*/
void hw_wifi_reset_connection(void);
/**
* @brief Get the IP address of the WiFi module.
*
* This function will return the 32-bit integer representation of the IP address
* of the WiFi module, assuming it is connected to a network. The returned address
* will be zero if it has not yet been assigned. The IP address is provided with
* a pointer to a struct with member 'addr' containing the 32-bit address.
*
* @param none
*
* @return pointer to the IP address struct for the WiFi module
*/
const ip_addr_t *hw_wifi_get_addr(void);
/**
* @brief Get the current status of the WiFi connection.
*
* This function will return the current status of the WiFi connection, given by
* the hw_wifi_status_t enum.
*
* @param none
*
* @return enumeration providing the current status of the WiFi connection
*/
hw_wifi_status_t hw_wifi_get_status(void);
#endif /* HW_WIFI_H */
================================================
FILE: hardware/rp2xxx/net_inc/lwipopts.h
================================================
#ifndef LWIPOPTS_H
#define LWIPOPTS_H
// Common settings used in most of the pico_w examples
// from: https://github.com/raspberrypi/pico-examples/blob/master/pico_w/wifi/lwipopts_examples_common.h
// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details)
// NO_SYS true, since we are running the entire lwIP stack in a single thread.
// This causes some issue with CYW43 init and blocking things like cyw43_arch_enable_sta_mode(),
// so watchdog timeout needs to be sufficiently long to allow for this, assuming WD is running.
#define NO_SYS 1
// allow override in some examples
#ifndef LWIP_SOCKET
#define LWIP_SOCKET 0
#endif
#if PICO_CYW43_ARCH_POLL
#define MEM_LIBC_MALLOC 1
#else
// MEM_LIBC_MALLOC is incompatible with non polling versions
#define MEM_LIBC_MALLOC 0
#endif
#define MEM_ALIGNMENT 4
#define MEM_SIZE 4000
#define MEMP_NUM_TCP_SEG 32
#define MEMP_NUM_ARP_QUEUE 10
#define PBUF_POOL_SIZE 24
#define LWIP_ARP 1
#define LWIP_ETHERNET 1
#define LWIP_ICMP 1
#define LWIP_RAW 1
#define TCP_WND (8 * TCP_MSS)
#define TCP_MSS 1460
#define TCP_SND_BUF (8 * TCP_MSS)
#define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS))
#define LWIP_NETIF_STATUS_CALLBACK 1
#define LWIP_NETIF_LINK_CALLBACK 1
#define LWIP_NETIF_HOSTNAME 1
#define LWIP_NETCONN 0
#define MEM_STATS 0
#define SYS_STATS 0
#define MEMP_STATS 0
#define LINK_STATS 0
// #define ETH_PAD_SIZE 2
#define LWIP_CHKSUM_ALGORITHM 3
#define LWIP_DHCP 1
#define LWIP_IPV4 1
#define LWIP_TCP 1
#define LWIP_UDP 1
#define LWIP_DNS 1
#define LWIP_TCP_KEEPALIVE 1
#define LWIP_NETIF_TX_SINGLE_PBUF 1
#define DHCP_DOES_ARP_CHECK 0
#define LWIP_DHCP_DOES_ACD_CHECK 0
#ifndef NDEBUG
#define LWIP_DEBUG 1
#define LWIP_STATS 1
#define LWIP_STATS_DISPLAY 1
#endif
#define ETHARP_DEBUG LWIP_DBG_OFF
#define NETIF_DEBUG LWIP_DBG_OFF
#define PBUF_DEBUG LWIP_DBG_OFF
#define API_LIB_DEBUG LWIP_DBG_OFF
#define API_MSG_DEBUG LWIP_DBG_OFF
#define SOCKETS_DEBUG LWIP_DBG_OFF
#define ICMP_DEBUG LWIP_DBG_OFF
#define INET_DEBUG LWIP_DBG_OFF
#define IP_DEBUG LWIP_DBG_OFF
#define IP_REASS_DEBUG LWIP_DBG_OFF
#define RAW_DEBUG LWIP_DBG_OFF
#define MEM_DEBUG LWIP_DBG_OFF
#define MEMP_DEBUG LWIP_DBG_OFF
#define SYS_DEBUG LWIP_DBG_OFF
#define TCP_DEBUG LWIP_DBG_OFF
#define TCP_INPUT_DEBUG LWIP_DBG_OFF
#define TCP_OUTPUT_DEBUG LWIP_DBG_OFF
#define TCP_RTO_DEBUG LWIP_DBG_OFF
#define TCP_CWND_DEBUG LWIP_DBG_OFF
#define TCP_WND_DEBUG LWIP_DBG_OFF
#define TCP_FR_DEBUG LWIP_DBG_OFF
#define TCP_QLEN_DEBUG LWIP_DBG_OFF
#define TCP_RST_DEBUG LWIP_DBG_OFF
#define UDP_DEBUG LWIP_DBG_OFF
#define TCPIP_DEBUG LWIP_DBG_OFF
#define PPP_DEBUG LWIP_DBG_OFF
#define SLIP_DEBUG LWIP_DBG_OFF
#define DHCP_DEBUG LWIP_DBG_OFF
/*******************************
* Application-specific settings
********************************/
// Enable mDNS responder
#define LWIP_MDNS_RESPONDER 1
#define LWIP_IGMP 1
#define LWIP_NUM_NETIF_CLIENT_DATA 1
#define MDNS_RESP_USENETIF_EXTCALLBACK 1
#define MEMP_NUM_SYS_TIMEOUT (LWIP_NUM_SYS_TIMEOUT_INTERNAL + 3)
#define MEMP_NUM_TCP_PCB 12
#ifdef ENABLE_HTTPD
// Enable cgi and ssi
#define LWIP_HTTPD_CGI 1
#define LWIP_HTTPD_SSI 1
#define LWIP_HTTPD_SSI_MULTIPART 1
// not necessary, can be done either way
#define LWIP_TCPIP_CORE_LOCKING_INPUT 1
// filename for httpd content generated with pico_set_lwip_httpd_content
#define HTTPD_FSDATA_FILE "pico_fsdata.inc"
#endif /* ENABLE_HTTPD */
#endif /* LWIPOPTS_H */
================================================
FILE: hardware/rp2xxx/onboard_flash.c
================================================
/******************************************************************************
* @file onboard_flash.c
*
* @brief Functions for accessing the 'onboard' flash memory (application
* storage mem). Intended to be used with a fixed chunk of leftover storage at
* the end of flash not used by the application. Note that the functions may be
* used standalone, however the argument format was structured so that they
* could be leveraged by LittleFS. The implementation of these functions is
* MCU-specific and will need to be changed if ported to a new hardware family.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include
#include "hardware_config.h"
#include "hardware/flash.h"
#include "hardware/regs/addressmap.h"
#include "hardware/sync.h"
#include "lfs.h"
#include "semphr.h"
const char* FLASH0_FS_BASE = (char*)(PICO_FLASH_SIZE_BYTES - FLASH0_FS_SIZE); // 'flash0' filesystem start address is at the end of flash
SemaphoreHandle_t onboard_flash_mutex; // global onboard flash mutex
void onboard_flash_init(void) {
// create onboard flash mutex
onboard_flash_mutex = xSemaphoreCreateMutex();
}
int onboard_flash_read(const struct lfs_config *c, uint32_t block, uint32_t offset, void* buffer, uint32_t size) {
if(xSemaphoreTake(onboard_flash_mutex, 10) == pdTRUE) { // try to acquire flash access
// copy from address of memory-mapped flash location - read address includes RAM offset XIP_NOCACHE_NOALLOC_BASE
memcpy(buffer, FLASH0_FS_BASE + XIP_NOCACHE_NOALLOC_BASE + (block * FLASH_SECTOR_SIZE) + offset, size); // note "block" and "sector" are synonomous here
xSemaphoreGive(onboard_flash_mutex);
return 0;
}
else return -1;
}
int onboard_flash_write(const struct lfs_config *c, uint32_t block, uint32_t offset, const void* buffer, uint32_t size) {
uint32_t addr = (uint32_t)FLASH0_FS_BASE + (block * FLASH_SECTOR_SIZE) + offset;
if(xSemaphoreTake(onboard_flash_mutex, 10) == pdTRUE) { // try to acquire flash access
uint32_t interrupts = save_and_disable_interrupts(); // disable interrupts since we are writing to program memory
flash_range_program(addr, buffer, size);
restore_interrupts(interrupts); // re-enable interrupts
xSemaphoreGive(onboard_flash_mutex);
return 0;
}
else return -1;
}
int onboard_flash_erase(const struct lfs_config *c, uint32_t block) {
uint32_t addr = (uint32_t)FLASH0_FS_BASE + (block * FLASH_SECTOR_SIZE);
if(xSemaphoreTake(onboard_flash_mutex, 10) == pdTRUE) { // try to acquire flash access
uint32_t interrupts = save_and_disable_interrupts(); // disable interrupts since we are erasing within program memory
flash_range_erase(addr, FLASH_SECTOR_SIZE); // erase entire block/sector
restore_interrupts(interrupts); // re-enable interrupts
xSemaphoreGive(onboard_flash_mutex);
return 0;
}
else return -1;
}
int onboard_flash_sync(const struct lfs_config *c) {
return 0; // always success, this is a dummy function
}
flash_usage_t onboard_flash_usage(void) {
flash_usage_t flash_usage; // the structure to hold flash usage details
extern char __flash_binary_end; // the last byte in the program binary (page-aligned)
uintptr_t prog_bin_end_addr = (uintptr_t) &__flash_binary_end; // the address of the binary upper bound
// calculate flash usage
flash_usage.flash_total_size = PICO_FLASH_SIZE_BYTES; // assuming the correct board/flash is identified by the SDK
flash_usage.program_used_size = prog_bin_end_addr - XIP_BASE;
flash_usage.fs_reserved_size = FLASH0_FS_SIZE;
flash_usage.flash_free_size = flash_usage.flash_total_size - flash_usage.program_used_size - flash_usage.fs_reserved_size;
return flash_usage;
}
================================================
FILE: hardware/rp2xxx/onboard_led.c
================================================
/******************************************************************************
* @file onboard_led.c
*
* @brief Functions to interact with the onboard LED GPIO. The implementation of
* these functions is MCU-specific and will need to be changed if ported
* to a new hardware family.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include "hardware_config.h"
#include "pico/stdlib.h"
#ifdef HAS_CYW43
#include "pico/cyw43_arch.h"
void onboard_led_init(void) {
// no work to do with CYW43 LED pin
}
void onboard_led_set(bool led_state) {
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, led_state);
}
bool onboard_led_get(void) {
return cyw43_arch_gpio_get(CYW43_WL_GPIO_LED_PIN);
}
#else
void onboard_led_init(void) {
gpio_init(PIN_NO_ONBOARD_LED);
gpio_set_dir(PIN_NO_ONBOARD_LED, GPIO_OUT);
}
void onboard_led_set(bool led_state) {
gpio_put(PIN_NO_ONBOARD_LED, led_state);
}
bool onboard_led_get(void) {
return gpio_get(PIN_NO_ONBOARD_LED);
}
#endif
================================================
FILE: hardware/rp2xxx/pico_sdk_import.cmake
================================================
# This is a copy of /external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
endif ()
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
endif()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
# GIT_SUBMODULES_RECURSE was added in 3.17
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
GIT_SUBMODULES_RECURSE FALSE
)
else ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
)
endif ()
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
FetchContent_Populate(pico_sdk)
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})
================================================
FILE: hardware/rp2xxx/prebuild.cmake
================================================
# PICO_BOARD should be one of the boards listed in Pico SDK header files
# see: https://github.com/raspberrypi/pico-sdk/tree/master/src/boards/include/boards
# Set BOARD in project.cmake accordingly (i.e. pico, pico_w, pico2, etc)
set(PICO_BOARD ${BOARD})
# Raspberry Pi Pico (RP2040/RP2350) SDK
include(${hardware_dir}/pico_sdk_import.cmake)
# Platform-specific variable for paths/libraries/etc
set(hardware_includes ${hardware_dir})
set(hardware_libs "pico_unique_id"
"pico_stdlib"
"hardware_i2c"
"hardware_spi"
"hardware_flash"
"hardware_adc"
"cmsis_core"
"tinyusb_device"
)
# FreeRTOS port subdirectory for this platform (relative to $FREERTOS_KERNEL_PATH)
# all of the "boards" from the link above should be handled here to categorize
# them as RP2040 or RP2350 type
if(PICO_BOARD STREQUAL "pico" OR PICO_BOARD STREQUAL "pico_w")
set(freertos_port_path "portable/ThirdParty/GCC/RP2040/FreeRTOS_Kernel_import.cmake")
elseif(PICO_BOARD STREQUAL "pico2" OR PICO_BOARD STREQUAL "pico2_w")
set(freertos_port_path "portable/ThirdParty/Community-Supported-Ports/GCC/RP2350_ARM_NTZ/FreeRTOS_Kernel_import.cmake")
else()
message(FATAL_ERROR "Board type unknown to BBOS: ${PICO_BOARD} - see hardware/rp2xxx/prebuild.cmake for instructions")
endif()
# If using a Pico wireless variant, include the cyw43 library
# Any other board names that use cyw43 would have to be added here too
if(PICO_BOARD STREQUAL "pico_w" OR PICO_BOARD STREQUAL "pico2_w")
add_compile_definitions(HAS_CYW43)
if(ENABLE_WIFI)
# include CY43 lwIP support
list(APPEND hardware_libs #"pico_cyw43_arch_lwip_sys_freertos" # for lwip NO_SYS=0
"pico_cyw43_arch_lwip_threadsafe_background" # for lwip NO_SYS=1
)
list(APPEND hardware_includes ${hardware_dir}/net_inc)
if(ENABLE_HTTPD)
list(APPEND hardware_libs "lwip_httpd_content"
"pico_lwip_mdns"
"pico_lwip_http"
)
list(APPEND hardware_includes ${PICO_LWIP_CONTRIB_PATH}/apps/httpd)
endif()
else()
list(APPEND hardware_libs pico_cyw43_arch_none) # basic CYW43 support
set(ENABLE_WIFI false)
endif()
else()
if(ENABLE_WIFI)
message(WARNING "Board does not support WiFi, disabling this option")
set(ENABLE_WIFI false)
endif()
endif()
# Make sure no networking features are enabled if WiFi is disabled
if(NOT ENABLE_WIFI AND ENABLE_HTTPD)
message(WARNING "Disabling httpd due to WiFi option being disabled")
set(ENABLE_HTTPD false)
endif()
================================================
FILE: hardware/rp2xxx/rtos_config.h
================================================
/******************************************************************************
* @file rtos_config.h
*
* @brief Hardware-specific configuration options for FreeRTOS, included by
* rtos/FreeRTOSConfig.h, which contains all the basic settings.
* Only FreeRTOS settings that are MCU-specific are defined here.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 01-14-2025
*
* @copyright Copyright (c) 2025 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#ifndef RTOS_CONFIG_H
#define RTOS_CONFIG_H
// FreeRTOS total heap size settings - trial and error to find what linker will accept
// RP2040 has 264KB RAM total, RP2350 has 520KB RAM total. RTOS heap will be something less than this.
// Using the wireless stack appears to chew up about 30-40KB.
#if defined(USING_RP2350) && !defined(HW_USE_WIFI)
#define RTOS_HEAP_SIZE (489*1024) // RP2350 without wireless stack
#elif defined(USING_RP2350) && defined(HW_USE_WIFI)
#define RTOS_HEAP_SIZE (439*1024) // RP2350 with wireless stack
#elif defined(USING_RP2040) && !defined(HW_USE_WIFI)
#define RTOS_HEAP_SIZE (235*1024) // RP2040 without wireless stack
#elif defined(USING_RP2040) && defined(HW_USE_WIFI)
#define RTOS_HEAP_SIZE (186*1024) // RP2040 with wireless stack
#endif
// For FreeRTOS SMP (multicore) support only
#define RTOS_NUM_CORES 1
#define RTOS_TICK_CORE 0
#define RTOS_RUN_MULTIPLE_PRIORITIES 1
#define RTOS_USE_CORE_AFFINITY 1
#define RTOS_USE_PASSIVE_IDLE_HOOK 0
// RP2040/RP2350-specific FreeRTOS settings
#define configSUPPORT_PICO_SYNC_INTEROP 1
#define configSUPPORT_PICO_TIME_INTEROP 1
// timer init to be called before runtime stats
#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()
extern uint64_t get_time_us(void); // extern declared so we can use here, defined in hw_clocks.c
// divider for proper runtime stats granularity
#define RUN_TIME_STATS_time_us_64_divider (1e6 / configTICK_RATE_HZ)
// function to use for run time stats timer
#define portGET_RUN_TIME_COUNTER_VALUE() (get_time_us() / RUN_TIME_STATS_time_us_64_divider)
// RP2350-specific - ARMv8m/Cortex-m33 options
#ifdef USING_RP2350
#define configENABLE_TRUSTZONE 0
#define configRUN_FREERTOS_SECURE_ONLY 1
#define configENABLE_FPU 1
#define configENABLE_MPU 0
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 16
#endif
#endif /* RTOS_CONFIG_H */
================================================
FILE: littlefs/CMakeLists.txt
================================================
# This file configures the build for the littlefs submodule, which is built
# as a static library to be used by the main application.
# The littlefs submodule is nested within another littlefs directory for ease
# of wrapping in CMake (littlefs project is Makefile-based)
# create littlefs library to use at top level
add_library(littlefs STATIC
${CMAKE_CURRENT_LIST_DIR}/littlefs/lfs_util.c
${CMAKE_CURRENT_LIST_DIR}/littlefs/lfs.c
)
target_include_directories(littlefs PUBLIC
${CMAKE_CURRENT_LIST_DIR}/littlefs
)
target_compile_options(
littlefs
PRIVATE
-Werror -g -O0
)
# TODO: try to add -Wall and -Wextra to compile options to clean up more warnings
================================================
FILE: main.c
================================================
/******************************************************************************
* @file main.c
*
* @brief Main entrypoint of the application.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include "hardware_config.h"
#include "device_drivers.h"
#include "services/services.h"
#include "task.h"
/************************
* Here be main
*************************/
void main()
{
// initialize hardware
hardware_init();
// initialize any connected peripheral devices
driver_init();
// register the taskmanager base service
taskman_service();
// start the rtos scheduler (boot the system!)
vTaskStartScheduler();
// will not reach here unless rtos crashes, reboot if that happens
force_watchdog_reboot();
}
================================================
FILE: project.cmake
================================================
# PROJECT NAME - in quotes, no spaces
set(PROJ_NAME "my-bbos-proj")
# PROJECT VERSION - in quotes, no spaces, can contain alphanumeric if necessary
set(PROJ_VER "0.0")
# CLI INTERFACE - 0: use UART for CLI (default), 1: use USB for CLI
set(CLI_IFACE 0)
# MCU PLATFORM - set the MCU platform being used (i.e. the subdir in 'hardware/')
set(PLATFORM rp2xxx)
# BOARD - set the board being used (platform-specific prebuild.cmake contains more information about boards)
set(BOARD pico2_w)
# HOSTNAME - hostname will be shown at CLI prompt, and used for network connections
set(HOSTNAME "bbos")
# BUILD OPTIONS - individual features which can be enabled or disabled
option(ENABLE_MOTD "Enable Message of the Day print at boot" true)
option(ENABLE_WIFI "Enable WiFi support" true)
option(ENABLE_HTTPD "Enable httpd web server" true)
================================================
FILE: rtos/CMakeLists.txt
================================================
# add source files to the top-level project
target_sources(${PROJ_NAME} PRIVATE
rtos_utils.c
)
================================================
FILE: rtos/FreeRTOSConfig.h
================================================
/******************************************************************************
* @file FreeRTOSConfig.h
*
* @brief Configuration file for FreeRTOS.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
*
* See rtos_config.h in the hardware/[platform]/ folder for platform-specific
* FreeRTOS configuration settings.
*
* FreeRTOS
* Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* https://www.FreeRTOS.org
* https://github.com/FreeRTOS
*
******************************************************************************/
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/*-----------------------------------------------------------
* Application specific definitions.
*
* These definitions should be adjusted for your particular hardware and
* application requirements.
*
* THESE PARAMETERS ARE DESCRIBED WITHIN THE 'CONFIGURATION' SECTION OF THE
* FreeRTOS API DOCUMENTATION AVAILABLE ON THE FreeRTOS.org WEB SITE.
*
* See http://www.freertos.org/a00110.html
*----------------------------------------------------------*/
#include "rtos_config.h"
/* Scheduler Related */
#define configUSE_PREEMPTION 1
#define configUSE_TICKLESS_IDLE 0
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
#define configMAX_PRIORITIES 32
#define configMINIMAL_STACK_SIZE ( configSTACK_DEPTH_TYPE ) 256
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
/* Synchronization Related */
#define configUSE_MUTEXES 1
#define configUSE_RECURSIVE_MUTEXES 1
#define configUSE_APPLICATION_TASK_TAG 0
#define configUSE_COUNTING_SEMAPHORES 1
#define configQUEUE_REGISTRY_SIZE 8
#define configUSE_QUEUE_SETS 1
#define configUSE_TIME_SLICING 1
#define configUSE_NEWLIB_REENTRANT 0
#define configENABLE_BACKWARD_COMPATIBILITY 1 // needed for lwip FreeRTOS compatibility
#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5
/* System */
#define configSTACK_DEPTH_TYPE uint32_t
#define configMESSAGE_BUFFER_LENGTH_TYPE size_t
/* Memory allocation related definitions. */
#define configSUPPORT_STATIC_ALLOCATION 0
#define configSUPPORT_DYNAMIC_ALLOCATION 1
#define configTOTAL_HEAP_SIZE RTOS_HEAP_SIZE // defined in hardware-specific CMakeLists.txt
#define configAPPLICATION_ALLOCATED_HEAP 0
/* Hook function related definitions. */
#define configCHECK_FOR_STACK_OVERFLOW 0
#define configUSE_MALLOC_FAILED_HOOK 0
#define configUSE_DAEMON_TASK_STARTUP_HOOK 0
/* Run time and task stats gathering related definitions. */
#define configGENERATE_RUN_TIME_STATS 1
#define configUSE_TRACE_FACILITY 1
#define configUSE_STATS_FORMATTING_FUNCTIONS 1
#define configRECORD_STACK_HIGH_ADDRESS 1
/* Co-routine related definitions. */
#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES 1
/* Software timer related definitions. */
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 1 )
#define configTIMER_QUEUE_LENGTH 10
#define configTIMER_TASK_STACK_DEPTH 1024
/* Interrupt nesting behaviour configuration. */
/*
#define configKERNEL_INTERRUPT_PRIORITY [dependent of processor]
#define configMAX_SYSCALL_INTERRUPT_PRIORITY [dependent on processor and application]
#define configMAX_API_CALL_INTERRUPT_PRIORITY [dependent on processor and application]
*/
/* SMP port only - this is in mainline FreeRTOS 11 and up */
/* these are defined in hardware/[platform]/rtos_config.h */
#define configNUMBER_OF_CORES RTOS_NUM_CORES
#define configTICK_CORE RTOS_TICK_CORE
#define configRUN_MULTIPLE_PRIORITIES RTOS_RUN_MULTIPLE_PRIORITIES
#if configNUMBER_OF_CORES > 1
#define configUSE_CORE_AFFINITY RTOS_USE_CORE_AFFINITY
#endif
#define configUSE_PASSIVE_IDLE_HOOK RTOS_USE_PASSIVE_IDLE_HOOK
#include
/* Define to trap errors during development. */
#define configASSERT(x) assert(x)
/* Set the following definitions to 1 to include the API function, or zero
to exclude the API function. */
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1
#define INCLUDE_xTaskGetSchedulerState 1
#define INCLUDE_xTaskGetCurrentTaskHandle 1
#define INCLUDE_uxTaskGetStackHighWaterMark 1
#define INCLUDE_xTaskGetIdleTaskHandle 1
#define INCLUDE_eTaskGetState 1
#define INCLUDE_xTimerPendFunctionCall 1
#define INCLUDE_xTaskAbortDelay 1
#define INCLUDE_xTaskGetHandle 1
#define INCLUDE_xTaskResumeFromISR 1
#define INCLUDE_xQueueGetMutexHolder 1
/* A header file that defines trace macro can be included here. */
#endif /* FREERTOS_CONFIG_H */
================================================
FILE: rtos/rtos_utils.c
================================================
/******************************************************************************
* @file rtos_utils.c
*
* @brief Utility functions to use for various RTOS interactions
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include "hardware_config.h"
#include "rtos_utils.h"
#include "FreeRTOSConfig.h"
#include "FreeRTOS.h"
#include "task.h"
void task_sched_update(uint32_t repeat, const TickType_t delay) {
#ifdef SCHED_TEST_DELAY
// used for OS scheduler testing - force task to take at least this
// much time for more usable data from vTaskGetRunTimeStats()
// set this to at least the OS tick rate
wait_here_us(1000);
#endif
if (xTaskGetTickCount() % repeat == 0) {
vTaskDelay(delay);
}
}
void task_delay_ms(uint32_t delay_ms) {
TickType_t delay_ticks = (TickType_t)(delay_ms * configTICK_RATE_HZ / 1000); // convert millisecond delay to OS ticks
vTaskDelay(delay_ticks);
}
================================================
FILE: rtos/rtos_utils.h
================================================
/******************************************************************************
* @file rtos_utils.h
*
* @brief Utility functions to use for various RTOS interactions
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#ifndef RTOS_UTILS_H
#define RTOS_UTILS_H
#include
#include "FreeRTOS.h"
#include "task.h"
/**
* @brief Check and update task against scheduler parameters.
*
* Checks the current task's OS scheduler info and compares against pre-defined
* schedule parameters (see services.h), blocking the task if necessary per the
* given parameters.
*
* @param repeat number of times to repeat a task, see services.h
* @param delay number of OS ticks to block a task, see services.h
*
* @return nothing
*/
void task_sched_update(uint32_t repeat, const TickType_t delay);
#endif
/**
* @brief Delay a task by a specified number of milliseconds.
*
* Converts the provided millisecond value into OS ticks, and calls the FreeRTOS
* vTaskDelay function to block the calling task for at least that amount of time.
*
* @param delay_ms time in milliseconds for the calling task to block itself
*
* @return nothing
*/
void task_delay_ms(uint32_t delay_ms);
================================================
FILE: services/CMakeLists.txt
================================================
# add source files to the top-level project
target_sources(${PROJ_NAME} PRIVATE
services.c
service_queues.c
cli_service.c
usb_service.c
taskman_service.c
storman_service.c
watchdog_service.c
heartbeat_service.c
)
if (ENABLE_WIFI)
target_sources(${PROJ_NAME} PRIVATE
netman_service.c
)
endif()
================================================
FILE: services/cli_service.c
================================================
/******************************************************************************
* @file cli_service.c
*
* @brief CLI service implementation and FreeRTOS task creation.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include
#include
#include "version.h"
#include "hardware_config.h"
#include "rtos_utils.h"
#include "shell.h"
#include "cli_utils.h"
#include "services.h"
#include "service_queues.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#ifndef PRINT_MOTD_AT_BOOT
#define PRINT_MOTD_AT_BOOT false
#endif
static void prvCliTask(void *pvParameters); // microshell cli task
TaskHandle_t xShellTask;
char BBOS_VERSION_MOD; // global "modified version" variable - declared in version.h
// main service function, creates FreeRTOS task from prvCliTask
BaseType_t cli_service(void)
{
BaseType_t xReturn;
shell_init(); // init microshell cli
// spawn the cli task
xReturn = xTaskCreate(
prvCliTask,
xstr(SERVICE_NAME_CLI),
STACK_CLI,
NULL,
PRIORITY_CLI,
&xShellTask
);
// print timestamp value
cli_uart_puts(timestamp());
if (xReturn == pdPASS) {
cli_uart_puts("CLI service started\r\n");
}
else {
cli_uart_puts("Error starting the CLI service\r\n");
}
return xReturn;
}
// FreeRTOS task created by cli_service
static void prvCliTask(void *pvParameters)
{
char print_string[PRINT_QUEUE_ITEM_SIZE];
extern const char *bbos_header_ascii;
// Set the global "modified version" indicator if on a branch other than main
BBOS_VERSION_MOD = strcmp(git_Branch(), "main") ? '+' : ' ';
// delay CLI startup to allow taskmanager to finish with its startup status prints
vTaskDelay(DELAY_TASKMAN * 5);
// print MOTD for additional YouTube likes
if (PRINT_MOTD_AT_BOOT) {
// initialize toasty graphics - make sure your MCU is liquid cooled...
cli_uart_puts(timestamp());
cli_uart_puts("Initializing toasty graphics");
for (int i = 0; i < 10; i++) {
wait_here_us(2E5); // waste a bunch of cycles for no reason
cli_uart_putc('.');
}
cli_uart_puts("\r\n");
print_motd();
}
// copy the CLI ASCII header into RAM
char *cli_header = pvPortMalloc(strlen(bbos_header_ascii+2)); // two extra bytes for BBOS_VERSION_MOD and NULL
strcpy(cli_header, bbos_header_ascii);
// add the "modified version" indicator to the header
memcpy( (cli_header + strlen(cli_header) - 78) , &BBOS_VERSION_MOD, 1); // manually offset to the correct position
// print the ASCII header before dropping into the CLI
shell_print(cli_header);
// free up the RAM
vPortFree(cli_header);
while(true) {
// peek into the print queue to see if an item is available
if (xQueuePeek(print_queue, (void *)&print_string, 0) == pdTRUE) {
// only pull an item from the queue if microshell is available to process it
// note that if any characters have been entered at the prompt, it will
// block prints from the queue so as not to interfere with user input
if (ush.state == USH_STATE_READ_CHAR && ush.in_pos == 0) {
if (xQueueReceive(print_queue, (void *)&print_string, 0) == pdTRUE) {
shell_print(print_string);
}
}
}
shell_service(); // the main cli service gets called forever in a loop
// update this task's schedule
task_sched_update(REPEAT_CLI, DELAY_CLI);
}
}
================================================
FILE: services/heartbeat_service.c
================================================
/******************************************************************************
* @file heartbeat_service.c
*
* @brief Heartbeat service implementation and FreeRTOS task creation.
* This can be used as a reference for how to create a simple "service"
* (FreeRTOS task), and how to pass output strings to the CLI service
* (in lieu of stdout/printf) via the 'cli_print' functions which utilize
* inter-task queues.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include "hardware_config.h"
#include "rtos_utils.h"
#include "services.h"
#include "service_queues.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
// when using this example to implement a new service, replace all references
// to "hearbeat" with the new service name.
static void prvHeartbeatTask(void *pvParameters); // entry function for this service, implementation below
TaskHandle_t xHeartbeatTask; // FreeRTOS task handle for this service
// service creation function, creates FreeRTOS task xHeartbeatTask from entry function prvHeartbeatTask
// this is the function pointer added to the service_functions[] array in services.h
BaseType_t heartbeat_service(void)
{
BaseType_t xReturn;
xReturn = xTaskCreate(
prvHeartbeatTask, // main function of this service, defined below
xstr(SERVICE_NAME_HEARTBEAT), // name defined in services.h
STACK_HEARTBEAT, // stack size defined in services.h
NULL, // parameters passed to created task (not used)
PRIORITY_HEARTBEAT, // priority of this service, defined in services.h
&xHeartbeatTask // FreeRTOS task handle
);
if (xReturn == pdPASS) {
cli_print_raw("heartbeat service started");
}
else {
cli_print_raw("Error starting the heartbeat service");
}
return xReturn;
}
// FreeRTOS task created by heartbeat_service
static void prvHeartbeatTask(void *pvParameters)
{
//
// Service startup (run once) code can be placed here
// (similar to Arduino setup(), if that's your thing)
//
static char *heartbeat_string = "ba-bump";
while(true) {
//
// Main service (run continuous) code can be placed here
// (similar to Arduino loop(), if that's your thing)
//
cli_print_timestamped(heartbeat_string);
// always include the below, with REPEAT & DELAY settings in services.h,
// otherwise the service will starve out other RTOS tasks
// update this task's schedule
task_sched_update(REPEAT_HEARTBEAT, DELAY_HEARTBEAT);
}
// the task runs forever unless the RTOS kernel suspends or kills it.
// for a one-shot service, just eliminate the infinite loop.
}
================================================
FILE: services/netman_service.c
================================================
/******************************************************************************
* @file netman_service.c
*
* @brief networkmanager service implementation and FreeRTOS task creation.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 01-24-2025
*
* @copyright Copyright (c) 2025 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include "hardware_config.h"
#include "rtos_utils.h"
#include "services.h"
#include "service_queues.h"
#include "shell.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "hw_wifi.h"
#include "hw_net.h"
static void prvNetworkManagerTask(void *pvParameters); // network manager task
TaskHandle_t xNetManTask;
struct netman_info_t nmi_glob; // global network manager status info
extern void shell_net_mount(void); // declared in this file so /net can be mounted on-the-fly
// main service function, creates FreeRTOS task from prvNetworkManagerTask
BaseType_t netman_service(void)
{
BaseType_t xReturn;
xReturn = xTaskCreate(
prvNetworkManagerTask,
xstr(SERVICE_NAME_NETMAN),
STACK_NETMAN,
NULL,
PRIORITY_NETMAN,
&xNetManTask
);
// print timestamp value
cli_uart_puts(timestamp());
if (xReturn == pdPASS) {
cli_uart_puts("Network manager service started\r\n");
} else {
cli_uart_puts("Error starting the network manager service\r\n");
}
return xReturn;
}
static void prvNetworkManagerTask(void *pvParameters) {
// network manager action queue item
netman_action_t nm_action;
// initialize the wireless module. Note that this is done here in the task
// because many network stacks/libraries are FreeRTOS-specific and so need
// to be initialized when the OS is already running.
if (hw_wifi_init()) { // todo: wifi country should be configurable
hw_wifi_enable_sta_mode();
cli_print_timestamped("WiFi hardware ready to connect");
shell_net_mount(); // create '/net' node in the shell
netman_request(NETJOIN); // auto-connect (will fail if '/net/wifi setauth' never used)
} else {
cli_print_timestamped("WiFi init failed");
}
while(true) {
// Check the networkmanager action queue to see if an item is available
if (xQueueReceive(netman_action_queue, (void *)&nm_action, 0) == pdTRUE)
{
// determine what action to perform
switch(nm_action)
{
case NETJOIN: // join a network (connect)
struct storman_item_t smi;
char *wifi_ssid;
char *wifi_pass;
bool continue_connect = false;
// make sure we aren't already connected
if (nmi_glob.status == HW_WIFI_STATUS_UP) {
cli_print_raw("already connected to network");
break;
}
// get network credentials from the filesystem
smi.action = CHKFILE;
strcpy(smi.sm_item_name, "wifi_auth");
storman_request(&smi);
// wait for storagemanager to provide semaphore indicating file exists
if (xSemaphoreTake(smi_glob_sem, DELAY_STORMAN * 2) == pdTRUE) {
// read credentials file
smi.action = DUMPFILE;
strcpy(smi.sm_item_name, "wifi_auth");
storman_request(&smi);
// wait for storagemanager to provide semaphore indicating data is ready
if (xSemaphoreTake(smi_glob_sem, DELAY_STORMAN * 2) == pdTRUE) {
// Split the credentials into SSID and password
wifi_ssid = strtok(smi_glob.sm_item_data, ",");
wifi_pass = strtok(NULL, ",");
// check for valid credentials
if (wifi_ssid == NULL || wifi_pass == NULL) {
cli_print_raw("invalid wifi credentials format");
break;
} else {
continue_connect = true;
}
}
} else {
// this will print if the semaphore was never given by storagemanager
cli_print_raw("no wifi credentials found");
break;
}
if (continue_connect) { // attempt to connect to the network
char connect_msg[60];
uint64_t start_time = get_time_us(); // used to set a timeout
bool timed_out = false;
sprintf(connect_msg, "connecting to %s network...", wifi_ssid);
if (hw_wifi_connect_async(wifi_ssid, wifi_pass, HW_WIFI_AUTH_WPA2_AES_PSK)) {
cli_print_raw(connect_msg);
while(hw_wifi_get_addr()->addr == 0) {
if (get_time_us() - start_time > 20000000) { // 20 seconds
timed_out = true;
hw_wifi_reset_connection();
break;
}
vTaskDelay(100); // spin every 100 OS ticks until connection is up
}
if (!timed_out) {
nmi_glob.status = HW_WIFI_STATUS_UP;
char ip_msg[32];
snprintf(ip_msg, 32, "wifi connected: %s", ip4addr_ntoa(hw_wifi_get_addr()));
cli_print_raw(ip_msg);
nmi_glob.ip = hw_wifi_get_addr()->addr;
/* enable optional networking features */
#ifdef ENABLE_HTTPD
net_mdns_init();
net_httpd_stack_init();
char httpd_msg[40+strlen(CYW43_HOST_NAME)];
sprintf(httpd_msg, "web console accessible at http://%s.local", CYW43_HOST_NAME);
cli_print_raw(httpd_msg);
#endif
/* end enable optional networking features */
} else {
cli_print_raw("wifi connection timed out");
}
} else {
cli_print_raw("could not start wifi connection");
}
}
break;
case NETLEAVE: // leave a network (disconnect)
if (nmi_glob.status == HW_WIFI_STATUS_UP) {
hw_wifi_reset_connection();
nmi_glob.status = HW_WIFI_STATUS_LINK_DOWN;
nmi_glob.ip = 0;
cli_print_raw("wifi disconnected");
} else {
cli_print_raw("not connected to a network");
}
break;
default:
break;
}
}
// update this task's schedule
task_sched_update(REPEAT_NETMAN, DELAY_NETMAN);
}
}
================================================
FILE: services/service_queues.c
================================================
/******************************************************************************
* @file service_queues.c
*
* @brief Initialization and helper functions for interaction with the queues
* used by FreeRTOS tasks.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include "services.h"
#include "service_queues.h"
#include "shell.h"
#include "FreeRTOS.h"
#include "queue.h"
// global declarations in services.h
QueueHandle_t print_queue;
QueueHandle_t taskman_queue;
QueueHandle_t storman_queue;
QueueHandle_t usb0_rx_queue;
QueueHandle_t usb0_tx_queue;
QueueHandle_t netman_action_queue;
// create task queues
bool init_queues(void) {
// initialize all queues
print_queue = xQueueCreate(PRINT_QUEUE_DEPTH, PRINT_QUEUE_ITEM_SIZE);
taskman_queue = xQueueCreate(TASKMAN_QUEUE_DEPTH, TASKMAN_QUEUE_ITEM_SIZE);
storman_queue = xQueueCreate(STORMAN_QUEUE_DEPTH, STORMAN_QUEUE_ITEM_SIZE);
usb0_rx_queue = xQueueCreate(USB0_RX_QUEUE_DEPTH, USB0_RX_QUEUE_ITEM_SIZE);
usb0_tx_queue = xQueueCreate(USB0_TX_QUEUE_DEPTH, USB0_TX_QUEUE_ITEM_SIZE);
#ifdef HW_USE_WIFI
netman_action_queue = xQueueCreate(NETMAN_ACTION_QUEUE_DEPTH, NETMAN_ACTION_QUEUE_ITEM_SIZE);
#endif
// make sure they were all created successfully
if (print_queue != NULL &&
taskman_queue != NULL &&
storman_queue != NULL &&
usb0_rx_queue != NULL &&
usb0_tx_queue != NULL &&
netman_action_queue != NULL ) {
return 0;
} else {
return 1;
}
}
/*******************************************
* helper functions to interact with queues
********************************************/
bool cli_print_raw(char *string) {
if (xQueueSend(print_queue, string, 10) == pdTRUE) { // add string to print queue, waiting 10 os ticks max
return true;
}
else return false;
}
bool cli_print_timestamped(char *string) {
bool queue_post_result;
char *timestamped_string = pvPortMalloc(TIMESTAMP_LEN + strlen(string));
sprintf(timestamped_string, timestamp());
sprintf(timestamped_string + strlen(timestamped_string), string);
if (xQueueSend(print_queue, timestamped_string, 10) == pdTRUE) { // add string to print queue, waiting 10 os ticks max
queue_post_result = true;
}
else queue_post_result = false;
vPortFree(timestamped_string);
return queue_post_result;
}
bool taskman_request(struct taskman_item_t *tmi) {
if (xQueueSend(taskman_queue, tmi, 10) == pdTRUE) { // add request item to taskmanager queue, waiting 10 os ticks max
return true;
}
else return false;
}
bool storman_request(struct storman_item_t *smi) {
if (xQueueSend(storman_queue, smi, 10) == pdTRUE) { // add request item to storagemanager queue, waiting 10 os ticks max
return true;
}
else return false;
}
#ifdef HW_USE_WIFI
bool netman_request(netman_action_t nma) {
if (xQueueSend(netman_action_queue, &nma, 10) == pdTRUE) { // add request item to networkmanager queue, waiting 10 os ticks max
return true;
}
else return false;
}
#endif /* HW_USE_WIFI */
bool usb_data_get(uint8_t *usb_rx_data) {
if (xQueueReceive(usb0_rx_queue, usb_rx_data, 0) == pdTRUE) { // try to get any data in queue immediately so as not to block
return true;
}
else return false;
}
bool usb_data_put(uint8_t *usb_tx_data) {
if (xQueueSend(usb0_tx_queue, usb_tx_data, 10) == pdTRUE) { // add data item to usb tx queue, waiting 10 os ticks max
return true;
}
else return false;
}
================================================
FILE: services/service_queues.h
================================================
/******************************************************************************
* @file service_queues.h
*
* @brief Initialization and helper functions for interaction with the queues
* used by services (FreeRTOS tasks) for inter-task communications and
* data handling.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#ifndef SERVICE_QUEUES_H
#define SERVICE_QUEUES_H
#include
#include "hardware_config.h"
#include "shell.h"
#include "FreeRTOS.h"
#include "semphr.h"
#include "queue.h"
#include "lfs.h"
#ifdef HW_USE_WIFI
#include "hw_wifi.h"
#endif
/**********************************************************
* CLI printing queue -
* allows tasks other than CLI to print to the CLI UART/USB
***********************************************************/
extern QueueHandle_t print_queue;
// note: if any characters are entered after the prompt, the CLI will not pull
// from the print queue... so the queue depth represents how many output strings
// can be buffered in the queue until current user input is completed
#define PRINT_QUEUE_DEPTH 32
#define PRINT_QUEUE_ITEM_SIZE BUF_OUT_SIZE
/*************************************************
* Task manager queue -
* used to pass commands for managing task control
**************************************************/
extern QueueHandle_t taskman_queue;
typedef enum tm_action_t {DELETE, SUSPEND, RESUME} tm_action_t; // action to take on task
typedef struct taskman_item_t {TaskHandle_t task; tm_action_t action;} taskman_item_t; // queue item - struct for taskID & action
#define TASKMAN_QUEUE_DEPTH 1
#define TASKMAN_QUEUE_ITEM_SIZE sizeof(taskman_item_t)
/*****************************************************************
* Storage manager queue -
* pass commands & data for interacting with onboard flash storage
******************************************************************/
extern QueueHandle_t storman_queue;
// storagemanager actions for interacting with littlefs
typedef enum {LSDIR, // list directory contents
MKDIR, // create a new directory
RMDIR, // delete a directory and its contents
MKFILE, // create a new empty file
RMFILE, // delete a file
DUMPFILE, // dump entire contents of file
READFILE, // read a portion of a file
WRITEFILE, // overwrite a file
APPENDFILE, // append data to end of file
FILESTAT, // get file statistics
CHKFILE, // check if a file exists without error
FSSTAT, // get filesystem statistics
FORMAT, // format the filesystem
UNMOUNT // unmount the filesystem and stop the service
} storman_action_t;
// the storagemanager queue item structure
typedef struct storman_item_t {storman_action_t action;
char sm_item_name[PATHNAME_MAX_LEN]; // file or directory name
lfs_soff_t sm_item_offset; // offset in file to read/write
long sm_item_size; // size of data to read/write
char sm_item_data[FILE_SIZE_MAX]; // file input/output data
struct lfs_info sm_item_info; // littlefs info structure
} storman_item_t;
// global structure to hold storagemanager status data
extern struct storman_item_t smi_glob;
// global storagemanager status binary semaphore - for data ready synchronization
extern SemaphoreHandle_t smi_glob_sem;
#define STORMAN_QUEUE_DEPTH 1
#define STORMAN_QUEUE_ITEM_SIZE sizeof(smi_glob)
#ifdef HW_USE_WIFI
/************************************************************
* Network manager queue -
* pass commands & data for interacting with network hardware
*************************************************************/
extern QueueHandle_t netman_action_queue;
// networkmanager actions for interacting with network hardware
typedef enum {NETJOIN, // join a network
NETLEAVE // leave a network
} netman_action_t;
// the networkmanager instance info structure
typedef struct netman_info_t {hw_wifi_status_t status; // network connection status
hw_wifi_ip_addr_t ip; // current IP address (IPv4)
} netman_info_t;
// global structure to hold networkmanager status info
extern struct netman_info_t nmi_glob;
#define NETMAN_ACTION_QUEUE_DEPTH 1
#define NETMAN_ACTION_QUEUE_ITEM_SIZE sizeof(netman_action_t)
#endif /* HW_USE_WIFI */
/**********************************************************
* USB device queue -
* holds buffered data bytes to or from the TinyUSB service
***********************************************************/
extern QueueHandle_t usb0_rx_queue;
extern QueueHandle_t usb0_tx_queue;
#define USB0_RX_QUEUE_DEPTH 8
#define USB0_TX_QUEUE_DEPTH 8
#define USB0_RX_QUEUE_ITEM_SIZE CFG_TUD_CDC_RX_BUFSIZE
#define USB0_TX_QUEUE_ITEM_SIZE CFG_TUD_CDC_TX_BUFSIZE
/************************
* Queue helper functions
*************************/
/**
* @brief Initialize all queues.
*
* This routine uses the FreeRTOS xQueueCreate API to create each queue used
* in the system. All queues are created even if the tasks that use them have
* not yet been started.
*
* @param none
*
* @return 0 upon success, 1 upon failure
*/
bool init_queues(void);
// queue helper functions
/**
* @brief Print a raw string out to the CLI.
*
* This function puts a string pointer into the CLI printing queue exactly as
* passed.
*
* @param string pointer to the string to put in the queue for printing
*
* @return nothing
*/
bool cli_print_raw(char *string);
/**
* @brief Print a timestamped string out to the CLI.
*
* This function prepends a timestamp onto the given string and then drops the
* new timestamped string pointer into the CLI printing queue.
*
* @param string pointer to the string to put in the queue for printing
*
* @return nothing
*/
bool cli_print_timestamped(char *string);
/**
* @brief Send a request to taskmanager.
*
* This helper function puts a taskmanager request item structure into the
* taskmanager service's queue for performing control actions on available
* services.
*
* @param tmi pointer to the taskmanager request item to put into the queue for
* controlling services
*
* @return true if item successfully queued, otherwise false (queue full)
*/
bool taskman_request(struct taskman_item_t *tmi);
/**
* @brief Send a request to storagemanager.
*
* This helper function puts a storagemanager request item structure into the
* storagemanager service's queue for interaction with a filesystem.
*
* @param smi pointer to the storagemanger request item to put into the queue for
* filesystem access
*
* @return true if item successfully queued, otherwise false (queue full)
*/
bool storman_request(struct storman_item_t *smi);
#ifdef HW_USE_WIFI
/**
* @brief Send a request to networkmanager.
*
* This helper function puts a networkemanager action request item into the
* networkmanager service's queue for interaction with a the network hardware.
*
* @param nma networkmanger request item to put into the queue for network
hardware interaction
*
* @return true if item successfully queued, otherwise false (queue full)
*/
bool netman_request(netman_action_t nma);
#endif /* HW_USE_WIFI */
/**
* @brief Get any data bytes in the USB RX buffer queue.
*
* This function will copy data (if available) out of the USB device RX queue.
* This data is in the form of a byte array up to the size of the USB RX buffer.
*
* @param usb_rx_data pointer to the buffer to copy the data from the rx queue into
*
* @return true if item retrieved, otherwise false (queue empty)
*/
bool usb_data_get(uint8_t *usb_rx_data);
/**
* @brief Put data bytes into the USB TX buffer queue.
*
* This function will put data to send into the USB TX queue. This data is a
* null-terminated byte array up to the size of the USB TX buffer.
*
* @param usb_rx_data pointer to the null-terminated data buffer to copy into the tx queue
*
* @return true if item successfully queued, otherwise false (queue full)
*/
bool usb_data_put(uint8_t *usb_tx_data);
#endif /* SERVICES_QUEUES_H */
================================================
FILE: services/services.c
================================================
/******************************************************************************
* @file services.c
*
* @brief Defines the service descriptors for each system service declared in
* services.h. These service descriptors are used to associate a service
* name string with a service function pointer, as well as indicate if
* the service should be started at boot. The individual service function
* implementations and FreeRTOS APIs for registering the associated tasks
* are contained in the source files for the respective services. Queues
* used to pass data between services are implemented in service_queues.h.
*
* @author @nbes4 (github)
*
* @date 06-18-2024
*
* @copyright Copyright (c) 2024 @nbes4 (github)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include "services.h"
// see services.h for more information on creating services
// note: use xstr() to convert the SERVICE_NAME_ #define from services.h to a usable string
// (e.g. xstr(SERVICE_NAME_HEARTBEAT))
const service_desc_t service_descriptors[] = {
{
.name = xstr(SERVICE_NAME_USB),
.service_func = usb_service,
.startup = true
},
{
.name = xstr(SERVICE_NAME_CLI),
.service_func = cli_service,
.startup = true
},
{
.name = xstr(SERVICE_NAME_STORMAN),
.service_func = storman_service,
.startup = true
},
#ifdef HW_USE_WIFI
{
.name = xstr(SERVICE_NAME_NETMAN),
.service_func = netman_service,
.startup = true
},
#endif /* HW_USE_WIFI */
{
.name = xstr(SERVICE_NAME_WATCHDOG),
.service_func = watchdog_service,
.startup = true
},
{
.name = xstr(SERVICE_NAME_HEARTBEAT),
.service_func = heartbeat_service,
.startup = false
}
};
const size_t service_descriptors_length = sizeof(service_descriptors)/sizeof(service_desc_t);
================================================
FILE: services/services.h
================================================
/******************************************************************************
* @file services.h
*
* @brief Defines all of the services available on the system. The individual
* service function implementations and FreeRTOS APIs for registering the
* associated tasks are contained in the source files for the respective
* services. Queues used to pass data between services are implemented
* in service_queues.h.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#ifndef SERVICES_H
#define SERVICES_H
#include
#include "hardware_config.h"
#include "shell.h"
#include "FreeRTOS.h"
#include "lfs.h"
/*******************************************************************************
* Services - Dynamic tasks to run in FreeRTOS.
* Services can be launched at boot or started/suspended/stopped at
* any time via 'taskmanager', the base service.
*
* If a new service is added, a SERVICE_NAME must be defined for it,
* and the service implementation and FreeRTOS task launcher must be
* created in a separate source file (see "heartbeat_service.c" for
* an example). The service's "descriptor" then needs to be added
* to the service_descriptors[] array in services.c, which associates
* the service's main function pointer with its name string, and
* defines whether the service should run at boot.
*
* The service schedule within FreeRTOS is determined by 3 parameters:
* Priority, Repeat, and Delay. Upon any given scheduler tick, the OS
* will run the task (service) with the highest priority. After a task
* runs, it will check if it should immediately run again based on the
* REPEAT parameter; if it has already repeated the specified number
* of times it will block itself for the specified number of ticks
* based on the DELAY parameter. This essentially means a task's run
* percentage (within a priority level) is given by REPEAT/DELAY.
*
* Pro tip: uncomment add_definitions(-DSCHED_TEST_DELAY) in the
* top-level CMakeLists.txt to burn extra cycles in each service, and
* then use the 'bin/top' CLI command to show FreeRTOS task runtime
* percentages to help tune the scheduler! Don't forget to comment
* this back out after testing!
*
* Note that by default, 1 OS tick is 1 ms.
* This can be changed in FreeRTOSConfig.h, see 'configTICK_RATE_HZ'
*******************************************************************************/
// stringify helper - use xstr() to convert #define to a usable string
#define str(s) # s
#define xstr(s) str(s)
// service names for spawning and interacting with rtos tasks - no quotes around them.
// note - max name length is 15 characters, defined by configMAX_TASK_NAME_LEN in FreeRTOS.h
#define SERVICE_NAME_TASKMAN taskmanager
#define SERVICE_NAME_CLI cli
#define SERVICE_NAME_USB usb
#define SERVICE_NAME_STORMAN storagemanager
#define SERVICE_NAME_NETMAN networkmanager
#define SERVICE_NAME_WATCHDOG watchdog
#define SERVICE_NAME_HEARTBEAT heartbeat
// freertos task priorities for the services.
// as long as configUSE_TIME_SLICING is set, equal priority tasks will share time.
#define PRIORITY_TASKMAN 1
#define PRIORITY_CLI 1
#define PRIORITY_USB 2
#define PRIORITY_STORMAN 3
#define PRIORITY_NETMAN 3
#define PRIORITY_WATCHDOG 1
#define PRIORITY_HEARTBEAT 1
// number of sequential time slices to run each service before beginning the
// delay interval set below. If a service should run most of the time, set REPEAT
// to a higher number and DELAY to a lower number.
#define REPEAT_TASKMAN 1
#define REPEAT_CLI 1
#define REPEAT_USB 1
#define REPEAT_STORMAN 1
#define REPEAT_NETMAN 1
#define REPEAT_WATCHDOG 1
#define REPEAT_HEARTBEAT 1
// OS ticks to block after each execution of a service (sets max execution interval).
// higher priority services should include some delay time to allow lower priority
// tasks to execute. Since the delay happens at the end of the service loop, the
// service should finish its current work before blocking itself with the delay.
// Note that if a service has delay of 0 and priority is > 0, the IDLE task will
// always be blocked and FreeRTOS will not be able to perform task cleanup (i.e. freeing RAM).
#define DELAY_TASKMAN 20
#define DELAY_CLI 1 // CLI delay could be increased at the expense of character I/O responsiveness
#define DELAY_USB 5
#define DELAY_STORMAN 100
#define DELAY_NETMAN 10 // This will impact network latency
#define DELAY_WATCHDOG 100
#define DELAY_HEARTBEAT 5000 // Example heartbeat service "beats" every 5 seconds when started
// FreeRTOS stack sizes for the services - "stack" in this sense is dedicated heap memory for a task.
// local variables within a service/task use this stack space.
// If pvPortMalloc is called within a task, it will allocate directly from shared FreeRTOS heap.
// Use 'bin/ps' command to show a service's min stack (memory usage high water mark)
// to determine if too much/too little has been allocated.
#define STACK_TASKMAN 512
#define STACK_CLI 1024
#define STACK_USB 1024
#define STACK_STORMAN 1024
#define STACK_NETMAN 1024
#define STACK_WATCHDOG configMINIMAL_STACK_SIZE // 256 by default
#define STACK_HEARTBEAT configMINIMAL_STACK_SIZE
/************************
* Service Functions
*************************/
// below are functions for launching the services.
// pointers to these are added to a service's .service_func descriptor item in
// services.c
/**
* @brief Start the taskmanager service.
*
* The taskmanager service is responsible for dynamically starting, suspending,
* stopping, and resuming services (FreeRTOS tasks). It does this by receiving a
* service name and action to perform from the taskmanager queue.
*
* @param none
*
* @return 32-bit integer corresponding to FreeRTOS return status defined in projdefs.h
*/
BaseType_t taskman_service(void);
/**
* @brief Start the CLI service.
*
* The CLI (Command Line Interface) service is an instance of microshell that
* provides the ability to interact with the system, run routines, and see text
* output using a serial terminal connected to a UART (or USB) on the MCU. It has
* additional wrappers and a printing queue which allows other services to print
* strings out from the CLI while remaining thread-safe.
*
* @param none
*
* @return 32-bit integer corresponding to FreeRTOS return status defined in projdefs.h
*/
BaseType_t cli_service(void);
/**
* @brief Start the USB service.
*
* The USB service manages the data pipe to/from the USB controller, operating in
* device mode, via the TinyUSB library. It is intended to run continuously at a
* low priority, checking for available TX/RX data in a non-blocking fashion.
* Data links between other tasks and USB endpoints are facilitated via data queues.
*
* @param none
*
* @return 32-bit integer corresponding to FreeRTOS return status defined in projdefs.h
*/
BaseType_t usb_service(void);
/**
* @brief Start the storagemanager service.
*
* The storagemanager service is responsible for maintaining the filesystem(s) in
* onboard flash that can be used to store persistent settings, logs, etc. It does
* this by implementing littlefs instances. All data transport to/from flash is
* done via the storagemanager queue.
*
* @param none
*
* @return 32-bit integer corresponding to FreeRTOS return status defined in projdefs.h
*/
BaseType_t storman_service(void);
/**
* @brief Start the networkmanager service.
*
* The networkmanager service is responsible for managing the network connection
* (currently only WiFi). It brings up/down WiFi hardware, manages credentials,
* and connects/disconnets to/from the wireless network. It also provides current
* network status upon request. The networkmanager also handles all network data
* transport via the networkmanager data queue.
*
* @param none
*
* @return 32-bit integer corresponding to FreeRTOS return status defined in projdefs.h
*/
BaseType_t netman_service(void);
/**
* @brief Start the watchdog service.
*
* The watchdog service enables the system watchdog timer, and then resets the
* timer periodically, preventing the timer from expiring and resetting the MCU.
* If for some reason we are stuck in the weeds somewhere and the watchdog
* cannot be serviced, a system reset will hopefully restore sanity.
*
* @param none
*
* @return 32-bit integer corresponding to FreeRTOS return status defined in projdefs.h
*/
BaseType_t watchdog_service(void);
/**
* @brief Start the heartbeat service.
*
* The heartbeat service is an example service which demonstrates the ability to
* dynamically start a task, and suspend/resume/kill it with the taskmanager.
* It also demonstrates the usage of the CLI printing queue by placing a string
* to print in the queue every 5 seconds (the heartbeat message) - 12 bpm - effectively
* simulating the heartbeat of a blue whale.
*
* @param none
*
* @return 32-bit integer corresponding to FreeRTOS return status defined in projdefs.h
*/
BaseType_t heartbeat_service(void);
/************************
* Service Descriptors
*************************/
// service function pointer typedef
typedef BaseType_t (*service_func_t)(void);
// service descriptor structure to hold the service name, service function pointer and startup flag
typedef struct service_desc_t {
// string version of the service name, used when comparing against user input
const char * const name;
//Defines wether or not this service should be automatically launched by taskmanager at boot
const bool startup;
//Function pointer to the service that creates the respective FreeRTOS task - declared in services.h
service_func_t service_func;
} service_desc_t;
// holds all the services that can be launched with taskmanager.
// these can be in any order, the corresponding FreeRTOS tasks will be launched in this order.
// note: taskmanager itself is not in this array (it is a base service).
// edit services.c to add your own services!
extern const service_desc_t service_descriptors[];
// number of service descriptors in the array is used to iterate through services
// for startup and interaction
extern const size_t service_descriptors_length;
#endif /* SERVICES_H */
================================================
FILE: services/storman_service.c
================================================
/******************************************************************************
* @file storman_service.c
*
* @brief storagemanager service implementation and FreeRTOS task creation.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include
#include "hardware_config.h"
#include "rtos_utils.h"
#include "shell.h"
#include "services.h"
#include "service_queues.h"
#include "hardware/flash.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
static void prvStorageManagerTask(void *pvParameters);
TaskHandle_t xStorManTask;
extern void shell_mnt_mount(void); // declared in this file so /mnt can be mounted on-the-fly
extern void shell_mnt_unmount(void); // declared in this file so /mnt can be unmounted on-the-fly
struct storman_item_t smi_glob; // write any resulting lfs data into global struct so it can be used by other tasks
SemaphoreHandle_t smi_glob_sem; // binary semaphore used to provide a data ready signal to other tasks
int err = 0; // used throughout the task to indicate that an error has occured
// main service function, creates FreeRTOS task from prvStorageManagerTask
BaseType_t storman_service(void)
{
BaseType_t xReturn;
// create the FreeRTOS task
xReturn = xTaskCreate(
prvStorageManagerTask,
xstr(SERVICE_NAME_STORMAN),
STACK_STORMAN,
NULL,
PRIORITY_STORMAN,
&xStorManTask
);
// print timestamp value
cli_uart_puts(timestamp());
if (xReturn == pdPASS) {
cli_uart_puts("Storage manager service running\r\n");
}
else {
cli_uart_puts("Error starting the storage manager service\r\n");
}
return xReturn;
}
// FreeRTOS task created by storman_service
static void prvStorageManagerTask(void *pvParameters)
{
// littlefs filesystem variables
lfs_t lfs_flash0;
lfs_file_t flash0_file;
lfs_dir_t flash0_dir;
// allocate memory for littlefs buffers
void *lfs_read_buffer = pvPortMalloc(FLASH0_PAGE_SIZE);
void *lfs_prog_buffer = pvPortMalloc(FLASH0_PAGE_SIZE);
void *lfs_lookahead_buffer = pvPortMalloc(FLASH0_LOOKAHEAD_SIZE);
// littlefs flash0 filesystem configuration
struct lfs_config fs_config_flash0 = {
// block device operation HAL functions
.read = onboard_flash_read,
.prog = onboard_flash_write,
.erase = onboard_flash_erase,
.sync = onboard_flash_sync,
// block device configuration
.read_size = 1,
.prog_size = FLASH0_PAGE_SIZE,
.block_size = FLASH0_BLOCK_SIZE,
.block_count = FLASH0_FS_SIZE / FLASH0_BLOCK_SIZE,
.block_cycles = FLASH0_BLOCK_CYCLES,
.cache_size = FLASH0_CACHE_SIZE,
.lookahead_size = FLASH0_LOOKAHEAD_SIZE,
// pre-allocated buffers
.read_buffer = lfs_read_buffer,
.prog_buffer = lfs_prog_buffer,
.lookahead_buffer = lfs_lookahead_buffer,
// other configurations
.name_max = PATHNAME_MAX_LEN, // max length of path+filenames
.file_max = FILE_SIZE_MAX, // max size of a file in bytes
};
// print some NVM initialization text
cli_uart_puts(timestamp());
cli_uart_puts("Initializing NVM...");
// mount the flash0 filesystem
if(lfs_mount(&lfs_flash0, &fs_config_flash0) == 0) {
shell_mnt_mount(); // create '/mnt' node in the shell
cli_uart_puts("flash0 mounted in /mnt\r\n");
} else {
// if non-zero, format and try again
cli_uart_puts("no filesystem found\r\n");
cli_uart_puts(timestamp());
cli_uart_puts("Formatting storage...\r\n"); // should only happen on first mount
if(lfs_format(&lfs_flash0, &fs_config_flash0) == 0) {
cli_uart_puts(timestamp());
cli_uart_puts("formatting complete - ");
if(lfs_mount(&lfs_flash0, &fs_config_flash0) == 0) { // retry mount
cli_uart_puts("flash0 mounted in /mnt\r\n");
} else {
cli_uart_puts("problem mounting flash0!\r\n");
}
} else {
cli_uart_puts(timestamp());
cli_uart_puts("problem formatting flash0!\r\n");
}
}
// create the binary semaphore used to signal other tasks that data is ready
smi_glob_sem = xSemaphoreCreateBinary();
while(true) {
err = 0;
// Check the storagemanager queue to see if an item is available
if (xQueueReceive(storman_queue, (void *)&smi_glob, 0) == pdTRUE)
{
// clear any existing semaphore in case a previous one was not taken
xSemaphoreTake(smi_glob_sem, 0);
// determine what action to perform in the filesystem.
// note that there may be further littlefs capabilities which are
// not implemented here. see "littlefs/lfs.h" for all APIs
switch(smi_glob.action)
{
case LSDIR: // list directory contents
err = lfs_dir_open(&lfs_flash0, &flash0_dir, smi_glob.sm_item_name);
if (err == 0) {
char *lsdir_output = (char*)pvPortMalloc(BUF_OUT_SIZE); // use FreeRTOS malloc and free, it is a large buffer
strcpy(lsdir_output, USH_SHELL_FONT_STYLE_BOLD
USH_SHELL_FONT_COLOR_BLUE
"File List\r\n"
"---------\r\n"
USH_SHELL_FONT_STYLE_RESET);
while(lfs_dir_read(&lfs_flash0, &flash0_dir, &smi_glob.sm_item_info) > 0) {
strcpy(lsdir_output + strlen(lsdir_output), smi_glob.sm_item_info.name);
if (smi_glob.sm_item_info.type == LFS_TYPE_DIR) { // check if this item is a directory
strcpy(lsdir_output + strlen(lsdir_output), "/"); // append a forward slash to the name if dir
}
strcpy(lsdir_output + strlen(lsdir_output), "\r\n");
};
strcpy(smi_glob.sm_item_data, lsdir_output); // use the global data to store a copy of the combined list output
smi_glob.sm_item_data[strlen(smi_glob.sm_item_data)] = 0; // put a null character at the end
xSemaphoreGive(smi_glob_sem); // provide the binary semaphore to indicate data is available
vPortFree(lsdir_output);
err = lfs_dir_close(&lfs_flash0, &flash0_dir);
}
break;
case MKDIR: // create a new directory
err = lfs_mkdir(&lfs_flash0, smi_glob.sm_item_name);
break;
case RMDIR: // delete a directory (only if empty)
// note: currently the same as RMFILE but kept separate for future enhancement (delete non-empty dirs, etc)
err = lfs_remove(&lfs_flash0, smi_glob.sm_item_name);
break;
case MKFILE: // create a new empty file (if file already exists, will generate error)
err = lfs_file_open(&lfs_flash0, &flash0_file, smi_glob.sm_item_name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL);
if (err < 0) break;
err = lfs_file_write(&lfs_flash0, &flash0_file, NULL, (lfs_size_t)1); // may not be needed?
if (err < 0) break;
err = lfs_file_close(&lfs_flash0, &flash0_file);
break;
case RMFILE: // delete a file
err = lfs_remove(&lfs_flash0, smi_glob.sm_item_name);
break;
case DUMPFILE: // dump entire contents of file
err = lfs_file_open(&lfs_flash0, &flash0_file, smi_glob.sm_item_name, LFS_O_RDONLY);
if (err < 0) break;
err = lfs_file_read(&lfs_flash0, &flash0_file, smi_glob.sm_item_data, sizeof(smi_glob.sm_item_data));
if (err < 0) break;
smi_glob.sm_item_data[lfs_file_size(&lfs_flash0, &flash0_file)] = 0; // put a null character at the end of the file contents data
xSemaphoreGive(smi_glob_sem); // provide the binary semaphore to indicate data is available
err = lfs_file_close(&lfs_flash0, &flash0_file);
break;
case READFILE: // read a portion of a file
err = lfs_file_open(&lfs_flash0, &flash0_file, smi_glob.sm_item_name, LFS_O_RDONLY);
if (err < 0) break;
err = lfs_file_seek(&lfs_flash0, &flash0_file, smi_glob.sm_item_offset, LFS_SEEK_SET);
if (err < 0) break;
err = lfs_file_read(&lfs_flash0, &flash0_file, smi_glob.sm_item_data, smi_glob.sm_item_size);
if (err < 0) break;
smi_glob.sm_item_data[smi_glob.sm_item_size] = 0; // put a null character at the end of the read data
xSemaphoreGive(smi_glob_sem); // provide the binary semaphore to indicate data is available
err = lfs_file_close(&lfs_flash0, &flash0_file);
break;
case WRITEFILE: // write to a new file, or overwrite an existing file
err = lfs_file_open(&lfs_flash0, &flash0_file, smi_glob.sm_item_name, LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC);
if (err < 0) break;
err = lfs_file_write(&lfs_flash0, &flash0_file, smi_glob.sm_item_data, (lfs_size_t)strlen(smi_glob.sm_item_data));
if (err < 0) break;
err = lfs_file_close(&lfs_flash0, &flash0_file);
break;
case APPENDFILE: // append data to end of existing file
err = lfs_file_open(&lfs_flash0, &flash0_file, smi_glob.sm_item_name, LFS_O_WRONLY | LFS_O_APPEND);
if (err < 0) break;
err = lfs_file_write(&lfs_flash0, &flash0_file, smi_glob.sm_item_data, (lfs_size_t)strlen(smi_glob.sm_item_data));
if (err < 0) break;
err = lfs_file_close(&lfs_flash0, &flash0_file);
break;
case FILESTAT: // get file statistics (size)
err = lfs_stat(&lfs_flash0, smi_glob.sm_item_name, &smi_glob.sm_item_info);
if (err < 0) break;
strcpy(smi_glob.sm_item_data, smi_glob.sm_item_info.name);
sprintf(smi_glob.sm_item_data + strlen(smi_glob.sm_item_data), ": %lu bytes", smi_glob.sm_item_info.size);
xSemaphoreGive(smi_glob_sem); // provide the binary semaphore to indicate data is available
break;
case CHKFILE: // check if a file exists without error
if (lfs_stat(&lfs_flash0, smi_glob.sm_item_name, &smi_glob.sm_item_info) == 0) {
xSemaphoreGive(smi_glob_sem); // provide the binary semaphore to indicate file exists
}
break;
case FSSTAT: // get filesystem statistics
smi_glob.sm_item_size = lfs_fs_size(&lfs_flash0);
if (smi_glob.sm_item_size < 0) {
err = smi_glob.sm_item_size;
break;
}
// format output for printing elsewhere
sprintf(smi_glob.sm_item_data, "Filesystem usage: %lu/%lu blocks (%lu/%lu bytes)",
smi_glob.sm_item_size,
FLASH0_FS_SIZE/FLASH0_BLOCK_SIZE,
smi_glob.sm_item_size*FLASH0_BLOCK_SIZE,
FLASH0_FS_SIZE);
xSemaphoreGive(smi_glob_sem); // provide the binary semaphore to indicate data is available
break;
case FORMAT: // format the filesystem (erase all contents)
if(lfs_format(&lfs_flash0, &fs_config_flash0) == 0 && lfs_mount(&lfs_flash0, &fs_config_flash0) == 0) {
strcpy(smi_glob.sm_item_data, "formatting complete");
}
else {
strcpy(smi_glob.sm_item_data, "problem formatting");
}
xSemaphoreGive(smi_glob_sem); // provide the binary semaphore to indicate data is available
break;
case UNMOUNT: // unmount the filesystem and stop the service
err = lfs_unmount(&lfs_flash0);
// free up buffers
vPortFree(lfs_read_buffer);
vPortFree(lfs_prog_buffer);
vPortFree(lfs_lookahead_buffer);
// remove the "/mnt" node from the CLI
shell_mnt_unmount();
break;
default:
break;
}
}
// check if there were any lfs errors and print to CLI
// error definitions come from enum lfs_error in lfs.h
if (err < 0) {
char err_msg[48] = "filesystem error: ";
switch (err)
{
case LFS_ERR_IO:
strcpy(err_msg + strlen(err_msg), "error during device operation");
break;
case LFS_ERR_CORRUPT:
strcpy(err_msg + strlen(err_msg), "corrupted");
break;
case LFS_ERR_NOENT:
strcpy(err_msg + strlen(err_msg), "entry does not exist");
break;
case LFS_ERR_EXIST:
strcpy(err_msg + strlen(err_msg), "entry already exists");
break;
case LFS_ERR_NOTDIR:
strcpy(err_msg + strlen(err_msg), "entry is not a dir");
break;
case LFS_ERR_ISDIR:
strcpy(err_msg + strlen(err_msg), "entry is a dir");
break;
case LFS_ERR_NOTEMPTY:
strcpy(err_msg + strlen(err_msg), "dir is not empty");
break;
case LFS_ERR_BADF:
strcpy(err_msg + strlen(err_msg), "bad file number");
break;
case LFS_ERR_FBIG:
strcpy(err_msg + strlen(err_msg), "file too large");
break;
case LFS_ERR_INVAL:
strcpy(err_msg + strlen(err_msg), "invalid parameter");
break;
case LFS_ERR_NOSPC:
strcpy(err_msg + strlen(err_msg), "no space left on device");
break;
case LFS_ERR_NOMEM:
strcpy(err_msg + strlen(err_msg), "no more memory available");
break;
case LFS_ERR_NOATTR:
strcpy(err_msg + strlen(err_msg), "no data/attr available");
break;
case LFS_ERR_NAMETOOLONG:
strcpy(err_msg + strlen(err_msg), "file name too long");
default:
strcpy(err_msg + strlen(err_msg), "unknown");
break;
}
cli_print_raw(err_msg);
}
if(smi_glob.action == UNMOUNT) {
// filesystem is already unmounted, delete this task
vTaskDelete(NULL);
}
// update this task's schedule
task_sched_update(REPEAT_STORMAN, DELAY_STORMAN);
}
}
================================================
FILE: services/taskman_service.c
================================================
/******************************************************************************
* @file taskman_service.c
*
* @brief taskmanager service implementation and FreeRTOS task creation.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include
#include "hardware_config.h"
#include "rtos_utils.h"
#include "shell.h"
#include "services.h"
#include "service_queues.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
static void prvTaskManagerTask(void *pvParameters);
TaskHandle_t xTaskManTask;
// main service function, creates FreeRTOS task from prvTaskManagerTask
BaseType_t taskman_service(void)
{
BaseType_t xReturn;
// create the FreeRTOS task
xReturn = xTaskCreate(
prvTaskManagerTask,
xstr(SERVICE_NAME_TASKMAN),
STACK_TASKMAN,
NULL,
PRIORITY_TASKMAN,
&xTaskManTask
);
// print timestamp value
cli_uart_puts(timestamp());
if (xReturn == pdPASS) {
// initialize all service queues
if (init_queues() == 0) {
cli_uart_puts("Task manager registered\r\n");
}
else {
cli_uart_puts("Error creating task queues\r\n");
}
}
else {
// if we got here, taskman service failed, no other services will start
cli_uart_puts("Error starting the taskmanager service\r\n");
}
return xReturn;
}
// FreeRTOS task created by taskman_service
static void prvTaskManagerTask(void *pvParameters)
{
struct taskman_item_t tmi;
// FreeRTOS scheduler is running by the time we get here. Print a message
cli_uart_puts(timestamp());
cli_uart_puts("FreeRTOS is running!\r\n");
// launch startup services
cli_uart_puts(timestamp());
cli_uart_puts("Starting all bootup services...\r\n");
int i;
for (i = 0; i < service_descriptors_length; i++) {
if(service_descriptors[i].startup) {
service_descriptors[i].service_func();
}
}
// At this point the system is fully running. Print a message
cli_uart_puts(timestamp());
cli_uart_puts("All startup services launched.\r\n");
while(true) {
// check for any task actions in the queue
if (xQueueReceive(taskman_queue, (void *)&tmi, 0) == pdTRUE) {
// perform the action. note that "START" is not implemented in task manager
// because an unregistered task does not have a TaskHandle yet (used
// in the taskman_item struct). This could be a future enhancement
switch (tmi.action)
{
case DELETE:
vTaskDelete(tmi.task);
break;
case SUSPEND:
vTaskSuspend(tmi.task);
break;
case RESUME:
vTaskResume(tmi.task);
break;
default:
break;
}
}
// update this task's schedule
task_sched_update(REPEAT_TASKMAN, DELAY_TASKMAN);
}
}
================================================
FILE: services/usb_service.c
================================================
/******************************************************************************
* @file usb_service.c
*
* @brief USB service implementation and FreeRTOS task creation.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include "hardware_config.h"
#include "rtos_utils.h"
#include "services.h"
#include "service_queues.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "tusb.h"
#include "string.h"
static void prvUsbTask(void *pvParameters); // usb task
TaskHandle_t xUsbTask;
// main service function, creates FreeRTOS task from prvUsbTask
BaseType_t usb_service(void)
{
BaseType_t xReturn;
// spawn the usb task
xReturn = xTaskCreate(
prvUsbTask,
xstr(SERVICE_NAME_USB),
STACK_USB,
NULL,
PRIORITY_USB,
&xUsbTask
);
// print timestamp value
cli_uart_puts(timestamp());
if (xReturn == pdPASS) {
cli_uart_puts("USB service started\r\n");
}
else {
cli_uart_puts("Error starting the USB service\r\n");
}
return xReturn;
}
// FreeRTOS task created by storman_service
static void prvUsbTask(void *pvParameters)
{
// USB device endpoint 0
struct usb_iface_t usb_iface_0 = {
.iface_id = 0,
.is_conn = false,
.rx_buffer = {},
.rx_pos = 0,
.tx_buffer = {},
.tx_pos = 0
};
while(true) {
// TinyUSB service function
tud_task();
// perform USB reads/writes and move data buffers to/from queue,
// only if not using CLI over USB. If using USB for CLI, run tud_task only
if (!CLI_USE_USB) {
// if connected, read/write any data available in buffers
if (tud_cdc_n_connected(usb_iface_0.iface_id)) {
usb_iface_0.is_conn = true;
usb_read_bytes(&usb_iface_0);
usb_write_bytes(&usb_iface_0);
}
else usb_iface_0.is_conn = false;
// check if there are RX bytes in the buffer and copy into the receive queue
if (usb_iface_0.rx_pos > 0) {
// add null termination to buffer if needed
if (usb_iface_0.rx_buffer[usb_iface_0.rx_pos - 1] != '\0') { // if it is a string there should already be NULL in pos-1
usb_iface_0.rx_buffer[usb_iface_0.rx_pos] = '\0'; // if not, add one
}
if (xQueueSend(usb0_rx_queue, &usb_iface_0.rx_buffer, 10) == pdTRUE) {
usb_iface_0.rx_pos = 0;
}
}
// check if there are any TX items in the queue and copy to the TX data buffer
if (usb_iface_0.tx_pos == 0) {
if (xQueueReceive(usb0_tx_queue, usb_iface_0.tx_buffer, 0) == pdTRUE) {
usb_iface_0.tx_pos = strlen(usb_iface_0.tx_buffer) + 1; // add 1 assuming null terminated byte array
}
}
}
// update this task's schedule
task_sched_update(REPEAT_USB, DELAY_USB);
}
}
================================================
FILE: services/watchdog_service.c
================================================
/******************************************************************************
* @file watchdog_service.c
*
* @brief watchdog service implementation and FreeRTOS task creation.
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#include "hardware_config.h"
#include "services.h"
#include "rtos_utils.h"
#include "FreeRTOS.h"
#include "task.h"
static void prvWatchdogTask(void *pvParameters);
TaskHandle_t xWatchdogTask;
// main service function, creates FreeRTOS task from prvWatchdogTask
BaseType_t watchdog_service(void)
{
BaseType_t xReturn;
// create the FreeRTOS task
xReturn = xTaskCreate(
prvWatchdogTask,
xstr(SERVICE_NAME_WATCHDOG),
STACK_WATCHDOG,
NULL,
PRIORITY_WATCHDOG,
&xWatchdogTask
);
// print timestamp value
cli_uart_puts(timestamp());
if (xReturn == pdPASS) {
cli_uart_puts("Watchdog service started\r\n");
}
else {
cli_uart_puts("Error starting the watchdog service\r\n");
}
return xReturn;
}
// FreeRTOS task created by watchdog_service
static void prvWatchdogTask(void *pvParameters)
{
// enable the watchdog timer
watchdog_en(WATCHDOG_DELAY_MS);
while(true) {
// reset watchdog timer
watchdog_kick();
// update this task's schedule
task_sched_update(REPEAT_WATCHDOG, DELAY_WATCHDOG);
}
}
================================================
FILE: version.h
================================================
/******************************************************************************
* @file version.h
*
* @brief contains the version number of the base BreadboardOS project
*
* @author Cavin McKinley (MCKNLY LLC)
*
* @date 02-14-2024
*
* @copyright Copyright (c) 2024 Cavin McKinley (MCKNLY LLC)
* Released under the MIT License
*
* SPDX-License-Identifier: MIT
******************************************************************************/
#ifndef VERSION_H
#define VERSION_H
// stringify helper - use xstr() to convert #define to a usable string
#define str(s) # s
#define xstr(s) str(s)
// maintain BreadboardOS version so it can be tracked along with custom project
#define BBOS_NAME "breadboard-os" // do not change
#define BBOS_VERSION_MAJOR 0 // changed only for major release update
#define BBOS_VERSION_MINOR 3 // changed only for minor release update
// global "modified version" variable will hold '+' if on a branch other than main
// this is checked and defined in the cli_service startup routine
extern char BBOS_VERSION_MOD;
// custom project name and version is defined in top level CMakeLists.txt
// look for PROJ_NAME and PROJ_VER
#endif /* VERSION_H */