10. Software
Embedded Hardware Software Lab – F’
Luke Clements
This lab was tested using F Prime release v3.4.1.
Table of Contents:
- Lab Description
- Prerequisites
- Specifying Requirements
- Setting up the Development Environment
- Arduino CLI Installation
- Project Setup
- Building for Arduino Microcontollers
- Component Design and Initial Implementation
- Initial Component Integration
- Continuing Component Implementation
- Full System Integration
- Running on Hardware
Lab Description
This lab is designed to provide an extended introductory tutorial on using F’ (F Prime) for embedded systems development, with a focus on running F’ on a microcontroller (like the one in the kit!). It will guide users through the process of creating components, handling events, telemetry, commands, and parameters. The goal is to help users understand the basics of F’ and integrate it with embedded hardware simulating Flight Software running on a spacecraft.
Prerequisites
-
- A computer running Linux, MacOS, or WSL 1 on Windows (see installation steps below).
- Note: If you have a windows machine you must use WSL1 and not WSL2
- Open PowerShell or Windows Command Prompt in administrator mode by right-clicking and selecting “Run as administrator”, enter the wsl –install command.
wsl --install
- Then restart your machine, open PowerShell or Windows Command Prompt in administrator mode by right-clicking and selecting “Run as administrator”, and run the following command:
wsl --set-default-version 1
- Note: If you get the error message “WSL 1 is not supported with your current Machine Configuration. Please enable the “Windows Subsystem for Linux” optional component to use WSL 1″
- Go to your Windows machines Control Panel -> Programs -> Programs and Features -> Turn Windows features on or off -> Select the check box for Windows Subsystem for Linux -> Press OK -> Restart Now -> the rerun the “wsl –set-default-version 1” command
- Finally, run the following command to update your WSL distribution:
sudo apt update && sudo apt upgrade
Important: If you are utilizing a Windows Computer for this lab, continue with the remaining prerequisites and lab steps in the WSL App.
- Git
- MacOS:
git --version
- If you don’t have it installed already, it will prompt you to install it.
- Linux/WSL:
sudo apt install git
- MacOS:
- CMake 3.16 or newer. CLI tools must be available on the system path.
- MacOS:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" && brew install cmake
- Linux/WSL:
sudo apt install cmake
- MacOS:
- CLang
- MacOS:
xcode-select --install
- Linux/WSL:
sudo apt install clang
- MacOS:
- Python 3.8+
- Mac
- Linux/WSL
- Ubuntu should come with python already installed you can make sure by running:
-
python3 --version
-
- Ubuntu should come with python already installed you can make sure by running:
- PIP
- Linux/WSL:
sudo apt install python3-pip
- Linux/WSL:
- Python Virtual Environments
- MacOS:
pip install virtualenv
- Linux/WSL:
sudo apt install python3-venv
- MacOS:
- Note: WSL and Linux users should see notes on Python installation if they are running into any issues with Python.
- Visual Studio, an integrated development environment (IDE). With the F’ plugin and WSL extension if you are using WSL1 for this lab.
- A computer running Linux, MacOS, or WSL 1 on Windows (see installation steps below).
Specifying Requirements
In this addendum to the lab, you will learn a bit about specifying requirements. Software requirements are derived from higher-level system requirements and represent the detail needed to implement the software.
The first step of the development process is to establish a high-level design for the project. This involves specifying system-level requirements and a block diagram that represents the key system functionality. Once complete the project should break this functionality into discrete units of functionality that represent the system. In addition, the interface between these units should be defined. The units of functionality are Components and the interfaces are further broken down into discrete calls or actions through that interface. These are F´ ports. The full design of the system of components and ports is the Topology.
Next, the project should review the components provided by the F´ framework to see what functionality can be inherited for free. This usually consists of the basic command and data handling components, the Os layer, drivers, and other Svc components. Where possible, these components should be used as-is to support a project to minimize extra work, but these may be cloned and owned if they fall short of project requirements.
The project now has a list of what components they must provide, and what they will inherit.
System Requirements
For this lab we have several higher-level system requirements. These requirements would be defined by requirements specified by the electronics subsystem which are themselves derived by requirements defined at the full system level.
Requirement | Description |
ELECTRONICS-001 | The system shall blink an LED in response to a command. |
ELECTRONICS-002 | The blink rate of the LED shall be changeable in-flight. |
Software Requirements
These are the requirements that the software team cares about. These dictate how the software should operate and trace to the higher-level system requirements. These also come with a verification method to ensure the software meets these requirements.
Ideally, the software engineer would be handed these requirements, however; in-practice this is often a discussion between the software engineer and their system engineers. A sample requirement is provided below.
Take a moment to identify some other requirements you might derive from the above electronics requirements. Do this before moving on to the next section.
LED Blinker
Here we list a number of requirements for our led software to implement.
Requirement | Description | Derived From | Verification |
LED-BLINKER-001 | The software shall start LED blinking in response to a command. | ELECTRONICS-001 | Unit Test |
LED-BLINKER-002 | The software shall stop LED blinking in response to a command. | ELECTRONICS-001 | Unit Test |
LED-BLINKER-003 | The software shall telemeter the current LED blinking state. | ELECTRONICS-001 | Unit Test |
LED-BLINKER-004 | The software shall emit events when the blinking state changes. | ELECTRONICS-001 | Unit Test |
LED-BLINKER-005 | The software shall store the blink interval as a parameter. | ELECTRONICS-002 | Unit Test |
LED-BLINKER-006 | The software shall blink the LED using the built-in LED or specified pin. | Electrical ICD | Unit Test |
Notice how the software also includes a requirement that derived from the Electrical Interface Control Document. This captures the details of the software/hardware interface and is captured here as a requirement.
Setting Up the Development Environment
The ecosystem of tools supporting F´ is installed as python packages available via PIP.
To use these tools we will use a Python virtual environment which is helpful in isolating project dependencies, ensuring version control, and avoiding conflicts between different projects. It streamlines development by providing a clean and reproducible environment, making it easier to manage dependencies.
Start by opening the appropriate command line interface for your operating system.
Note: If you are utilizing WSL1, please ensure that you have the VS Code WSL extension installed. Next, launch VS Code, look for this icon in the bottom left ><, and choose “WSL: Connect to WSL” from the menu.
- Create a project folder and navigate to it:
mkdir fprime_project
cd fprime_project
- Create the virtual environment:
python3 -m venv fprime-venv
- Activate the virtual environment:
. fprime-venv/bin/activate
Reminder: Activate the virtual environment whenever you work with this F´ project or start a new command line interface session.
Arduino CLI Installation
This guide will walk through the installation of the arduino-cli and arduino-cli-cmake-wraper components used to bridge F Prime and the Arduino build system. This assumes a virtual environment has been set up for your project.
Note: If you haven’t done so yet, open your command line interface and activate your virtual environment.
Install arduino-cli:
This command downloads arduino-cli and installs the binary into the existing (and activated) virtual environment:
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=$VIRTUAL_ENV/bin sh
Install arduino-cli-wrapper:
pip install arduino-cli-cmake-wrapper
Setup arduino-cli for select Arduino boards:
The following list of boards were tested. You are free to add your own board manager URL to your configuration if you are using a board that is not listed here:
- PJRC Teensy (Teensy 3.2, Teensy 4.0, Teensy 4.1)
- Adafruit Feather M0
- ESP32 Dev Module
- Raspberry Pi Pico W (RP2040)
- Adafruit Feather RP2040
- SparkFun Thing Plus RP2040
- ATmega128 (with external memory, not the stock 2K memory)
Initialize the arduino-cli configuration file:
arduino-cli config init
Below are board manager URLs for select Arduino boards. You are not required to add all of these boards, just the board you plan on using:
arduino-cli config add board_manager.additional_urls https://www.pjrc.com/teensy/package_teensy_index.json
arduino-cli config add board_manager.additional_urls https://adafruit.github.io/arduino-board-index/package_adafruit_index.json
arduino-cli config add board_manager.additional_urls https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
arduino-cli config add board_manager.additional_urls https://mcudude.github.io/MegaCore/package_MCUdude_MegaCore_index.json
arduino-cli config add board_manager.additional_urls https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
Next, Update the Arduino CLI core index by running the following command:
arduino-cli core update-index
After updating the core index, you can install one of the following boards using the corresponding command. Only install the ones you have added to your board manager in the previous step.
For Teensy AVR boards, run:
arduino-cli core install teensy:avr
For Adafruit SAMD boards, run:
arduino-cli core install adafruit:samd
For ESP32 boards, run:
arduino-cli core install esp32:esp32
For MegaCore AVR boards, run:
arduino-cli core install MegaCore:avr
For RP2040 boards, run:
arduino-cli core install rp2040:rp2040
Adding udev rules (Native Linux Machines Only)
If you are using a linux machine download/save the .rules files located here for your selected board(s) into /etc/udev/rules.d/ on your computer.
Project Setup
If you’re not already inside the directory you created earlier in the lab, simply use the ‘cd’ command to navigate to the newly created ‘fprime_project directory’ in your command line interface.
Reminder: Activate the virtual environment whenever you work with this F´ project or start a new command line interface session.
Once you are there the next step is to install the F’ tools:
pip install -U fprime-tools
Some macOS users see an SSL error. Correct the SSL error and rerun the above command.
Now that the tools are installed a new F´ project should be created. An F´ project internalizes the version of F´ that the project will build upon and provides the user the basic setup for creating, building, and testing components.
Then set up a new F´ project using the fprime-util new –project command. Please select a project name of led-blinker and F´ version of devel.
fprime-util new --project
This command will ask you to enter a name for the project. For this lab we will use ‘led-blinker’.
[1/1] Project name (MyProject): led-blinker |
Once you’ve entered the project name, the command will momentarily pause to execute, requiring some time as it initiates the cloning process of the core F´ repository. This command results in the establishment of a fresh F´ project structure, neatly organized within a designated folder named “led-blinker.”
If you receive the following warning, “[WARNING] requirements.txt has not been installed because you are not running in a virtual environment.” Run the following command:
# In fprime_project/led-blinker
pip install -Ur fprime/requirements.txt
If you want you can navigate to the project’s directory and look around:
cd led-blinker
ls
This will show the following files:
- fprime/: F´ repository. Contains core F´ components, the API for the build system, among others
- settings.ini: allows users to set various settings to control the build
- CMakeList.txt and project.cmake: CMake files defining the build system
- Components/: directory to place user components in
In order to ensure functionality of these instructions, the lab procedures were tested on F Prime version v3.4.1. To ensure seamless usage, we will proceed to update F Prime to the latest tested version.
# In fprime_project/led-blinker
cd fprime
git checkout v3.4.1 && git pull
Building for Arduino Microcontrollers
This section will guide you on converting your project to build for Arduino microcontrollers using the fprime-arduino package.
Adding fprime-arduino
First, add the fprime-arduino package as a submodule into your project root.
git submodule add https://github.com/fprime-community/fprime-arduino.git
Once you have added the submodule it is time to move over to Visual Studio Code.
If you are using WSL follow these next steps to connect VS Code to WSL.
- Press F1, select WSL: Connect to WSL
- Use the File menu to open your Led-Blinker lab folder.
Then, locate the ‘settings.ini’ file inside the ‘led-blinker’ project directory as we are to add two additional lines.
- Add fprime-arduino as a library
- For the examples in this lab we will be using a Teensy 4.1, thus will add the default build toolchain as teensy41
library_locations: ./fprime-arduino default_toolchain: teensy41 |
library_locations: ./fprime-arduino
- This line specifies the location of a library called “fprime-arduino.” It tells the project where to find this library. The library is expected to be in the current directory (./) and inside a subdirectory named “fprime-arduino.”
default_toolchain: teensy41
- This line specifies that the default toolchain for building the project is configured for the Teensy 4.1 microcontroller.
The “toolchain” in this context typically includes:
- A compiler: This is a program that translates your source code into machine code that the microcontroller can understand.
- Linker: This tool combines compiled code and libraries into an executable program.
- Standard libraries: Libraries that provide essential functions and features for your code to interact with the microcontroller’s hardware.
- Configuration settings: These settings define the target microcontroller, memory allocation, and other low-level details necessary for proper compilation.
By setting the “default_toolchain” to “teensy41,” you are telling your build system to use the toolchain and configurations that are appropriate for compiling code to run on a Teensy 4.1 microcontroller. This ensures that your code will be compatible with the hardware and can take full advantage of its features.
Note: If you are using a different board for this lab change teensy41 to your desired board.
These are the boards the lab has been tested with:
- Teensy 4.0 (teensy40)
- Teensy 4.1 (teensy41)
- Adafruit Feather M0 (featherM0)
- Adafruit Feather RP2040 (featherrp2040rfm)
- SparkFun Thing Plus RP2040 (thingplusrp2040)
- ESP32 Dev Module (esp32)
- Raspberry Pi Pico (rpipico)
- Raspberry Pi Pico W (rpipicow)
After you have added those two lines your file should look like this:
[fprime] project_root: . framework_path: ./fprime library_locations: ./fprime-arduino default_toolchain: teensy41default_cmake_options:FPRIME_ENABLE_FRAMEWORK_UTS=OFF FPRIME_ENABLE_AUTOCODER_UTS=OFF |
Finally, save the ‘settings.ini’ file to ensure your changes are applied.
Modifying F´ Configuration Files (Optional)
If you are building for a system with low memory (< 100KB RAM), this step is recommended.
Copy the default F´ configuration files into your project root:
cp -r fprime/config . |
Reference the config files in this repository and modify your config files accordingly. Or, you may clone this repository and copy the entire config directory into your project.
Then redirect the configuration path in your led-blinker/settings.ini by adding the following line:
config_directory: ./config |
Test Deployment
In your IDE, open a new command line interface.
To do this in VS Code Using the Menu:
- Go to the “Terminal” menu in the top toolbar.
- Select “New Terminal” from the dropdown menu.
Reminder: Activate your virtual environment since you have opened a new command line interface session:
. fprime-venv/bin/activate
Then test if your project builds by running the following commands:
fprime-util generate fprime-util build |
When running “fprime-util generate” if you get the following error:
CMake Error at fprime/cmake/required.cmake:23 (message): -- Configuring incomplete, errors occurred! fpp-tools version incompatible. Found v1.2.0, expected v1.3.0. Install with: 'pip install -r "/fprime_project/led-blinker/fprime/requirements.txt"' Call Stack (most recent call first): fprime/cmake/FPrime.cmake:26 (include) CMakeLists.txt:13 (include) |
- First, find the command ‘pip install -r “… requirements.txt”‘ in the error message. Copy it.
- In your terminal, paste that command and run it.
- After running the command in your terminal, delete the folder called “build-fprime-automatic” in your project. You can do this by right clicking on the folder in VS Code’s explorer tab and selecting “delete”.
- Then, proceed by running the two commands: first, run “fprime-util generate” and then run “fprime-util build.”
Reminder: Activate your virtual environment when opening a new command line interface.
Note: Make sure to save all the files you’ve edited before running `generate` and `build` commands.
If you wish to test other Arduino builds, append the build name after fprime-util generate and fprime-util build. Tested boards are listed below (build name in parenthesis):
- Teensy 4.0 (teensy40)
- Teensy 4.1 (teensy41)
- Adafruit Feather M0 (featherM0)
- Adafruit Feather RP2040 (featherrp2040rfm)
- SparkFun Thing Plus RP2040 (thingplusrp2040)
- ESP32 Dev Module (esp32)
- Raspberry Pi Pico (rpipico)
- Raspberry Pi Pico W (rpipicow)
If you see:
[100%] Built target Arduino_Drv_HardwareRateDriver |
That means your project was built successfully!
Conclusion
Congratulations! You are now able to build an F´ deployment for Arduino microcontrollers! The next step is to create your LED Component.
Note: You can no longer build for the native system as this is now an Arduino specific deployment.
Component Design and Initial Implementation
The purpose of this exercise is to walk you through the creation and initial implementation of an F´ component to control the blinking of an LED. This section will discuss the design of the full component, the implementation of a command to start/stop the LED blinking, and the sending of events. Users will then proceed to the initial ground testing before finishing the implementation in a later section.
Component Design
In order for our component to blink an LED, it needs to accept a command to turn on the LED and drive a GPIO pin via a port call to the GPIO driver. It will also need a rate group input port to control the timing of the blink. Additionally, we will define events and telemetry channels to report component state, and a parameter to control the period of the blink.
This component design is captured in the block diagram below with input ports on the left and output ports on the right. Ports for standard F´ functions (e.g. commands, events, telemetry, and parameters) are circled in green.
In this exercise, the BLINKING_ON_OFF command shall toggle the blinking state of the LED. The period of the blinking is controlled by the BLINK_INTERVAL parameter. Blinking is implemented on the run rate group input port. The component also defines several telemetry channels and events describing the various actions taken by the component.
Design Summary
Component Ports:
- run: invoked at a set rate from the rate group, used to control the LED blinking
- gpioSet: invoked by the Led component to control the GPIO driver
Standard component ports (circled in green) are not listed here.
Commands:
- BLINKING_ON_OFF: turn the LED blinking on/off
Events:
- InvalidBlinkArgument: emitted when an invalid argument was supplied to the BLINKING_ON_OFF command
- SetBlinkingState: emitted when the component sets the blink state
- BlinkIntervalSet: emitted when the component blink interval parameter is set
- LedState: emitted when the LED is driven to a new state
Telemetry Channels:
- BlinkingState: state of the LED blinking
- LedTransitions: count of the LED transitions
Parameters:
- BLINK_INTERVAL: LED blink period in number of rate group calls
Create the component
It is time to create the basic component. In a terminal, navigate to the project’s root directory and run the following:
# In led-blinkermkdir -p Components cd Components fprime-util new --component |
You will be prompted for information regarding your component. Fill out the prompts as shown below:
[INFO] Cookiecutter source: using builtin component_name [MyComponent]: Led component_short_description [Example Component for F Prime FSW framework.]: Component to blink an LED driven by a rate group component_namespace [Led]: Components Select component_kind: 1 - active 2 - passive 3 - queued Choose from 1, 2, 3 [1]: 2 Select enable_commands: 1 - yes 2 - no Choose from 1, 2 [1]: 1 Select enable_telemetry: 1 - yes 2 - no Choose from 1, 2 [1]: 1 Select enable_events: 1 - yes 2 - no Choose from 1, 2 [1]: 1 Select enable_parameters: 1 - yes 2 - no Choose from 1, 2 [1]: 1 [INFO] Found CMake file at 'led-blinker/project.cmake' Add component Led to led-blinker/project.cmake at end of file (yes/no)? yes Generate implementation files (yes/no)? yes |
Your new component is located in the directory led-blinker/Components/Led.
Component State
Many of the behaviors of the component discussed in the Component Design section require the tracking of some state. Before diving into the implementation of the behavior let us set up and initialize that state.
Open ‘Led.hpp’ in ‘led-blinker/Components/Led’ and include the following private member variables at line 34.
In C++, comments are denoted by the double forward slash “//”. Comments serve as annotations within the code to provide explanatory notes and insights. It’s crucial that you pay close attention to these comments within code blocks, as they offer valuable information about the purpose and functionality of specific sections. By carefully reviewing the comments, you can gain a better understanding of the code’s logic and function.
PRIVATE:
|
Now, open ‘Led.cpp’ in ‘led-blinker/Components/Led’ and initialize your member variables in the constructor, replacing lines 26 to 28 with the following lines:
Led ::Led(const char* const compName) : LedComponentBase(compName), state(Fw::On::OFF), transitions(0), count(0), blinking(true) {} |
Now that the member variables are set up, we can continue into the component implementation.
The above code will fail to find the Fw::On enum type until we use it in the FPP model in the next section. To fix immediately, add
#include <Fw/Types/OnEnumAc.hpp> |
to the top of Led.hpp.
Commands
Commands are used to command the component from the ground system or a command sequencer. We will add a command named BLINKING_ON_OFF to turn on or off the blinking LED. This command will take in an argument named on_off of type Fw.On.
Inside your led-blinker/Components/Led directory, open the file Led.fpp and search for the following:
# @ Example async command # async command COMMAND_NAME(param_name: U32) |
Replace lines 9-10 with the following block:
@ Command to turn on or off the blinking LED sync command BLINKING_ON_OFF( on_off: Fw.On @< Indicates whether the blinking should be on or off ) |
Next, run the following in the led-blinker/Components/Led directory:
# In led-blinker/Components/Ledfprime-util impl |
This command will generate two files automatically: ‘Led.hpp-template’ and ‘Led.cpp-template’. These files contain the stub implementation for the component, which should now include stubs for this newly added command.
Now, let’s move the content from our newly created template files to the corresponding Led component files:
# In led-blinker/Components/Ledmv Led.cpp-template Led.cpp mv Led.hpp-template Led.hpp |
Once you have done that open the Led.hpp file and paste the following private member variables:
Fw::On state; //! Keeps track if LED is on or off U64 transitions; //! The number of on/off transitions that have occurred from FSW boot up U32 count; //! Keeps track of how many ticks the LED has been on for bool blinking; //! Flag: if true then LED blinking will occur else no blinking will happen |
After pasting the code block, your private section should appear as follows:
PRIVATE:
|
We will now proceed to implement the functionality for the BLINKING_ON_OFF command. Open your Led.cpp file and copy the provided code snippet and paste it within the {} replacing the // TODO section of the function added in the preceding step.
// Create a variable to represent the command response auto cmdResp = Fw::CmdResponse::OK;// Verify if on_off is a valid argument. // Note: isValid is an auto generated helper function for enums defined in fpp. if(!on_off.isValid()) { // TODO: Add an event that indicates we received an invalid argument. // NOTE: Add this event after going through the "Events" exercise.// Update command response with a validation error cmdResp = Fw::CmdResponse::VALIDATION_ERROR; } else { this->count = 0; // Reset count on any successful command this->blinking = Fw::On::ON == on_off; // Update blinking state// TODO: Add an event that reports the state we set to blinking. // NOTE: This event will be added during the "Events" exercise.// TODO: Report the blinking state via a telemetry channel. // NOTE: This telemetry channel will be added during the "Telemetry" exercise. }// Provide command response this->cmdResponse_out(opCode,cmdSeq,cmdResp); |
Save your files, and then in the VS Code terminal, run this command to make sure your component builds properly:
# In led-blinker/Components/Ledfprime-util build |
Fix any errors that occur before proceeding with the rest of the lab.
Events
Events represent a log of system activities. Events are typically emitted any time the system takes an action. Events are also emitted to report off-nominal conditions. Our component has four events, two that this section will show and two are left to the student.
Returning to your ‘led-blinker/Components/Led’ directory, open the ‘Led.fpp’ file. Replace the sample event lines at 17 and 18 with these two events
@ Indicates we received an invalid argument. event InvalidBlinkArgument(badArgument: Fw.On) \ severity warning low \ format "Invalid Blinking Argument: {}"@ Reports the state we set to blinking. event SetBlinkingState(state: Fw.On) \ severity activity high \ format "Set blinking state to {}." |
Save the file and in the terminal, run the following to verify your component is building correctly.
# In led-blinker/Components/Ledfprime-util build |
Resolve any errors before continuing.
Let’s open the ‘Led.cpp’ file in your ‘led-blinker/Components/Led’ directory. Find the ‘BLINKING_ON_OFF’ command, and we’ll insert code to report an error in the input argument using our new event.
To do so, replace:
// TODO: Add an event that indicates we received an invalid argument. // NOTE: Add this event after going through the "Events" exercise. |
With:
this->log_WARNING_LO_InvalidBlinkArgument(on_off); |
Similarly, use an event to report the blinking state has been set.
Replace the following:
// TODO: Add an event that reports the state we set to blinking. // NOTE: This event will be added during the "Events" exercise. |
With:
this->log_ACTIVITY_HI_SetBlinkingState(on_off); |
Save the file and in the terminal, run the following to verify your component is building correctly.
# In led-blinker/Components/Ledfprime-util build |
To wrap up this section, let’s introduce two additional events: a ‘BlinkIntervalSet’ event with an argument of U32 type to signal when the interval parameter is set, and a ‘LedState’ event with an argument of Fw.On type to indicate changes in the LED state.
Open your ‘Led.fpp’ file and add these events below the ones you’ve previously created.
@ Event logged when the LED blink interval is updated event BlinkIntervalSet(interval: U32) \ severity activity high \ format "LED blink interval set to {}"@ Event logged when the LED turns on or off event LedState(on_off: Fw.On) \ severity activity low \ format "LED is {}" |
Save the file and in the terminal, run the following to verify your component is building correctly.
# In led-blinker/Components/Ledfprime-util build |
Resolve any errors before continuing.
Conclusion
Congratulations! You have now implemented some basic functionality in a new F´ component.
Initial Component Integration
In this section, users will create a deployment and perform the initial integration of the LED component into that deployment. This deployment will automatically include the basic command and data handling setup needed to interact with the component. Wiring the Led component to the GPIO driver component will be covered in a later section after the component implementation has finished.
Users must have created the initial Led component implementation in order to run through this section. Users may continue to define commands, events, telemetry, and ports after this initial integration.
Creating the LedBlinker Deployment
In order to produce an executable to run the software, users need to create a deployment. A deployment is one software executable that contains the main entry point, and an F´ system topology. We will be using a custom deployment that auto generates an Arduino deployment.
To begin, insert the following line into the ‘led-blinker/settings.ini’ file. You can place it immediately after the default toolchain line:
deployment_cookiecutter: https://github.com/fprime-community/fprime-arduino-deployment-cookiecutter.git |
Next, create a new deployment in the led-blinker directory with:
# In led-blinkerfprime-util new --deployment |
This will ask for some input, respond with the following answers:
[INFO] Cookiecutter source: https://github.com/fprime-community/fprime-arduino-deployment-cookiecutter.git [1/1] deployment_name (fprime-arduino-deployment): LedBlinker [INFO] Found CMake file at 'led-blinker/project.cmake' Add component Test to led-blinker/project.cmake at end of file? (yes/no) [yes]: yes |
Note: The link to the cookiecutter source. It must show the fprime-arduino-deployment-cookiecutter for a successful Arduino deployment. If the cookiecutter shows ‘using builtin template’, then the cookiecutter has failed.
In order to check that the deployment was created successfully, the user can generate a build cache and build the deployment. This will generate and build the code for the current host system, not the remote embedded hardware allowing a local test during development.
# In led-blinkerfprime-util build |
Adding Led Component To The Deployment
The component can now be added to the deployment’s topology effectively adding this component to the running system. This is done by modifying instances.fpp and topology.fpp in the Top directory.
Add the following to led-blinker/LedBlinker/Top/instances.fpp. Typically, this is added to the “Passive component instances” section of that document.
instance led: Components.Led base id 0x10000 |
This defines an instance of the Led component called led.
Next, the topology needs to use the above definition. This is done by adding the following to the list of instances defined in led-blinker/LedBlinker/Top/topology.fpp.
instance led |
After, it should look something like this:
topology LedBlinker { ... instance ... instance led ... } |
No port connections need to be added because thus far the component only defines standard ports and those are connected automatically.
This includes the large project (e.g. Components) in this deployment’s build.
Conclusion
Congratulations! You have now integrated your component and tested that integration.
This lab will return to the component implementation before finishing the integration of the component and testing on hardware.
Continuing Component Implementation
In this section, we will complete the component implementation by transmitting a telemetry channel, and implementing the behavior of the run port, which is called by the rate-group.
Refer back to the component design for explanations of what each of these items is intended to do.
Telemetry Channels
Telemetry channels represent the state of the system. Typically, telemetry channels are defined for any states that give crucial insight into the component’s behavior. In this section we will define two channels.
Within your ‘led-blinker/Components/Led’ directory, open the ‘Led.fpp’ file. Instead of the “example telemetry counter,” replace it with the following lines:
@ Telemetry channel to report blinking state. telemetry BlinkingState: Fw.On@ Telemetry channel counting LED transitions telemetry LedTransitions: U32 |
Save the file. In the terminal, run the following to verify your component is building correctly.
# In led-blinker/Components/Ledfprime-util build |
Fix any errors that occur before proceeding with the rest of the lab.
Inside your led-blinker/Components/Led directory, open Led.cpp, and navigate to the BLINKING_ON_OFF command. Report the blinking state via the telemetry channel we just added. To do so, replace the following:
// TODO: Report the blinking state via a telemetry channel. // NOTE: This telemetry channel will be added during the "Telemetry" exercise. |
with the command to send the telemetry channel:
this->tlmWrite_BlinkingState(on_off); |
Adding Led Channels To the Packet Specification
Some users choose to send telemetry packets instead of raw channels to the ground system. Although this lab will not use telemetry packets, it is best practice to keep the packet definitions up-to-date to make switching to telemetry packets seamless should the user choose to do so.
Add the following to led-blinker/LedBlinker/Top/LedBlinkerPackets.xml:
<packet name="LedChannels" id="8" level="1"> <channel name="led.LedTransitions"/> <channel name="led.BlinkingState"/> </packet> |
Now that this has been added, build the topology:
# In led-blinker/LedBlinkerfprime-util build |
Fix any errors before continuing.
Parameters
Parameters are ground-controllable settings for the system. Parameters are used to set settings of the system that the ground may need to change at some point during the lifetime of the system. This lab sets one parameter, the blink interval.
For each parameter you define in your fpp, the F´ autocoder will auto generate a SET and SAVE command. The SET command allows the ground to update the parameter. The SAVE command allows ground to save the current value of the parameter for use even after FSW reboots.
Inside your ‘led-blinker/Components/Led’ directory, open the ‘Led.fpp’ file. Replace the “Example parameter” and create a parameter named BLINK_INTERVAL with a type of U32.
@ Blinking interval in rate group ticks param BLINK_INTERVAL: U32 |
Save the file. In the terminal, run the following to verify your component is building correctly.
# In led-blinker/LedBlinkerfprime-util build |
In your led-blinker/Components/Led directory, open the file Led.hpp and add the following public function signature after the destructor:
//! Emit parameter updated EVR //! void parameterUpdated(FwPrmIdType id /*!< The parameter ID*/ ); |
After you have done that part of your file should look something like this:
// ... (previous code)
|
This function is called when a parameter is updated via the generated SET command. Although the value is updated automatically, this function gives developers a chance to respond to changing parameters. This lab uses it to emit an updated Event.
Save file and in your led-blinker/Components/Led directory, open Led.cpp and add the implementation for parameterUpdated after the destructor:
void Led ::parameterUpdated(FwPrmIdType id) { // Read back the parameter value Fw::ParamValid isValid; U32 interval = this->paramGet_BLINK_INTERVAL(isValid); // NOTE: isValid is always VALID in parameterUpdated as it was just properly set FW_ASSERT(isValid == Fw::ParamValid::VALID, isValid);// Check the parameter ID is expected if (PARAMID_BLINK_INTERVAL == id) { // Emit the blink interval set event this->log_ACTIVITY_HI_BlinkIntervalSet(interval); } } |
After you have done that part of your file should look something like this:
// ... (previous code) Led ::Led(const char* const compName) : LedComponentBase(compName), state(Fw::On::OFF), transitions(0), count(0), blinking(true) {}Led ::~Led() {}void Led ::parameterUpdated(FwPrmIdType id) { // Read back the parameter value Fw::ParamValid isValid; U32 interval = this->paramGet_BLINK_INTERVAL(isValid); // NOTE: isValid is always VALID in parameterUpdated as it was just properly set FW_ASSERT(isValid == Fw::ParamValid::VALID, isValid);// Check the parameter ID is expected if (PARAMID_BLINK_INTERVAL == id) { // Emit the blink interval set event this->log_ACTIVITY_HI_BlinkIntervalSet(interval); } } //------------------------------------------------------------------- // Handler implementations for user-defined typed input ports //------------------------------------------------------------------- // ... (subsequent code) |
When you are done, save the file. In the terminal, run the following to verify your component is building correctly.
# In led-blinker/Components/Ledfprime-util build |
Resolve any errors before continuing.
Additional Ports
Any communication between components should be accomplished through F´ ports. Thus far we have been using a set of standard ports for handling Commands, Telemetry, Events, and Parameters. This section will add two specific ports to our component: input run to be called from the rate group, and output gpioSet to drive the GPIO driver.
In your led-blinker/Components/Led directory, open the Led.fpp file. Replacing the “Example Port”, add the following two ports:
@ Port receiving calls from the rate group sync input port run: Svc.Sched@ Port sending calls to the GPIO driver output port gpioSet: Drv.GpioWrite |
Input ports can be given any name that you choose. In this example, we choose run and gpioSet since these names capture the behavioral intent. The types of Svc.Sched and Drv.GpioWrite are significant as these types must match the remote component.
In your led-blinker/Components/Led directory, run the following to auto generate stub functions for the run input port we just added.
# In led-blinker/Components/Ledfprime-util impl |
In your led-blinker/Components/Led directory, open Led.hpp-template file and copy this block over to Led.hpp as we did before:
//! Handler implementation for run //! void run_handler( const NATIVE_INT_TYPE portNum, /*!< The port number*/ NATIVE_UINT_TYPE context /*!< The call order */ ); |
After you have done that part of your file should look something like this:
// ... (previous code)
|
In your led-blinker/Components/Led directory, open Led.cpp-template file and copy this block over to Led.cpp:
void Led :: run_handler( const NATIVE_INT_TYPE portNum, NATIVE_UINT_TYPE context ) { // TODO } |
After you have done that part of your file should look something like this:
// ... (previous code)
|
The run port will be invoked repeatedly on each cycle of the rate group. Each invocation will call into the run_handler function such that the component may perform behavior on each cycle.
Here we want to turn the LED on or OFF based on a cycle count to implement the “blinking” behavior we desire.
Inside your ‘led-blinker/Components/Led’ directory, open the ‘Led.cpp’ file. Copy and paste the following code block into the “// TODO” section of the “run_handler” function. Try your best to fill in the “// TODOs” based on the information and definitions learned from the previous sections.
Don’t forget to read the code and comments to understand more about how to use F´.
// Read back the parameter value Fw::ParamValid isValid; U32 interval = 0; // TODO: Get BLINK_INTERVAL parameter value// Force interval to be 0 when invalid or not set interval = ((Fw::ParamValid::INVALID == isValid) || (Fw::ParamValid::UNINIT == isValid)) ? 0 : interval;// Only perform actions when set to blinking bool is_blinking = this->blinking; if (is_blinking) { Fw::On new_state = this->state; // Check for transitions if ((0 == this->count) && (this->state == Fw::On::OFF)) { new_state = Fw::On::ON; } else if (((interval / 2) == this->count) && (this->state == Fw::On::ON)) { new_state = Fw::On::OFF; }// A transition has occurred if (this->state != new_state) { this->transitions = this->transitions + 1; // TODO: Add a telemetry to report the number of LED transitions (this->transitions)// Port may not be connected, so check before sending output if (this->isConnected_gpioSet_OutputPort(0)) { this->gpioSet_out(0, (Fw::On::ON == new_state) ? Fw::Logic::HIGH : Fw::Logic::LOW); }// TODO: Add an event to report the LED state (new_state). this->state = new_state; }this->count = ((this->count + 1) >= interval) ? 0 : (this->count + 1); } |
Note: If you get stuck on the // TODOs you can find the complete “run_handler” below:
void Led ::run_handler(const NATIVE_INT_TYPE portNum, NATIVE_UINT_TYPE context) { // Read back the parameter value Fw::ParamValid isValid; U32 interval = this->paramGet_BLINK_INTERVAL(isValid);// Force interval to be 0 when invalid or not set interval = ((Fw::ParamValid::INVALID == isValid) || (Fw::ParamValid::UNINIT == isValid)) ? 0 : interval;// Only perform actions when set to blinking bool is_blinking = this->blinking; if (is_blinking) { Fw::On new_state = this->state; // Check for transitions if ((0 == this->count) && (this->state == Fw::On::OFF)) { new_state = Fw::On::ON; } else if (((interval / 2) == this->count) && (this->state == Fw::On::ON)) { new_state = Fw::On::OFF; }// A transition has occurred if (this->state != new_state) { this->transitions = this->transitions + 1; this->tlmWrite_LedTransitions(this->transitions);// Port may not be connected, so check before sending output if (this->isConnected_gpioSet_OutputPort(0)) { this->gpioSet_out(0, (Fw::On::ON == new_state) ? Fw::Logic::HIGH : Fw::Logic::LOW); }this->log_ACTIVITY_LO_LedState(new_state); this->state = new_state; }this->count = ((this->count + 1) >= interval) ? 0 : (this->count + 1); } } |
Save the file and in the terminal, run the following to verify your component is building correctly.
# In led-blinker/Components/Ledfprime-util build |
Resolve any errors and finish any TODOs before continuing.
Conclusion
Congratulations! You just completed the implementation of your component. It is time to finish implementation and run on hardware!
Full System Integration
Now it is time to add a GPIO driver to our system and attach it to the led component instance. We’ll also connect the led component instance to a 1 Hz rate group. Finally, we’ll configure the driver to manage the GPIO 13 pin on our hardware. Once this section is finished, the system is ready for running on hardware.
Adding the GPIO Driver to the Topology
fprime-arduino provides a GPIO driver for Arduino microcontrollers called Arduino.GpioDriver. This should be added to both the instance definition list and the topology instance list just like we did for the led component. Since the GPIO driver is a passive component, its definition is a bit more simple.
Add to “Passive Component” section of led-blinker/LedBLinker/Top/instances.fpp:
instance gpioDriver: Arduino.GpioDriver base id 0x4C00 |
Add to the instance list of led-blinker/LedBLinker/Top/topology.fpp:
instance gpioDriver |
In led-blinker build the deployment and resolve any errors before continuing.
Wiring The LED Component Instance to the GPIO Component Instance and Rate Group
The Led component defines the gpioSet output port and the Arduino.GpioDriver defines the gpioWrite input port. These two ports need to be connected from output to input. The ActiveRateGroup component defines an array of ports called RateGroupMemberOut and one of these needs to be connected to a run port defined on the Led component.
We can create a named connections block in the topology and connect the two port pairs. Remember to use the component instances and not the component definitions for each connection.
To do this, add the following lines to led-blinker/LedBLinker/Top/topology.fpp:
# Named connection group connections LedConnections { # Rate Group 1 (1Hz cycle) output is connected to led's run input rateGroup1.RateGroupMemberOut[3] -> led.run # led's gpioSet output is connected to gpioDriver's gpioWrite input led.gpioSet -> gpioDriver.gpioWrite } |
rateGroup1 is preconfigured to call all RateGroupMemberOut at a rate of 1 Hz. We use index RateGroupMemberOut[3] because RateGroupMemberOut[0] through RateGroupMemberOut[2] were used previously in the RateGroups connection block.
Configuring The GPIO Driver
So far the GPIO driver has been instantiated and wired, but has not been told what GPIO pin to control. For this lab, the built-in LED will be used. To configure this, the open function needs to be called in the topology’s C++ implementation and passed the pin’s number and direction.
This is done by adding the following line to the “configureTopology” function defined in led-blinker/LedBLinker/Top/LedBLinkerTopology.cpp.
gpioDriver.open(Arduino::DEF_LED_BUILTIN, Arduino::GpioDriver::GpioDirection::OUT); |
After you have done that part of your file should look something like this:
void configureTopology() { // Rate group driver needs a divisor list rateGroupDriver.configure(rateGroupDivisors, FW_NUM_ARRAY_ELEMENTS(rateGroupDivisors));// Rate groups require context arrays. rateGroup1.configure(rateGroup1Context, FW_NUM_ARRAY_ELEMENTS(rateGroup1Context));// Framer and Deframer components need to be passed a protocol handler framer.setup(framing); deframer.setup(deframing); #ifndef NO_ONBOARD_LED gpioDriver.open(Arduino::DEF_LED_BUILTIN, Arduino::GpioDriver::GpioDirection::OUT); #endif } |
This code tells the GPIO driver to open pin LED_BUILTIN (usually pin 13) as an output pin. If your device does not have a built-in LED, select a GPIO pin of your choice.
In led-blinker build the deployment and resolve any errors before continuing.
# In led-blinkerfprime-util build |
Conclusion
Congratulations! You’ve wired your component to the rate group driver and GPIO driver components. It is time to try it on hardware.
Running on Hardware
It’s time to run on hardware. Connect the microcontroller to your host machine via USB. First, upload the binary/hex file to the board after building it. If the files don’t upload automatically, follow this guide.
To initiate the F´ GDS, execute the following command. This command initializes the GDS without triggering the native compilation (-n), utilizes the dictionary from the previously crafted build (–dictionary ./build-artifacts/<build name>/LedBlinkerTopologyAppDictionary.xml), and establishes a connection to the USB device by including the –comm-adapter, –uart-device, and –uart-baud flags.
Note: Be sure to adjust the command to match the correct path to your dictionary.xml file and replace TEENSY_NAME with your Teensy’s name. You can find your Teensy’s name by running:
On WSL:
- Open the Device Manager program on your computer.
- Find the “Ports” section within Device Manager. Look specifically for something called “COM ports.”
- Your microcontroller should be listed as a “USB Serial Device” under the Ports section.
- Once you find it, look for the COM port associated with your microcontroller. In this example, let’s say it’s COM3. In this case, the Teensy’s name would be /dev/ttyS3.
On Linux/MacOS:
ls /dev/tty* |
You might notice it showing up as something like: /dev/tty.usbmodem…..
GDS command:
# In the project rootfprime-gds -n --dictionary ./build-artifacts/teensy41/LedBlinker/dict/LedBlinkerTopologyAppDictionary.xml --comm-adapter uart --uart-device /dev/TEENSY_NAME --uart-baud 115200 |
Note: For MacOS users, you may need to install pyserial:
pip install pyserial |
Note: If your build is not for Teensy 4.1, change “teensy41” to the appropriate build name (e.g., teensy32, featherM0, esp32, etc.).
Once you’ve done this, you should see a green circle appear in the top right corner of the F´ GDS.
Testing the Topology
Test the component integration with the following steps:
- Verify connection: confirm that there is a green circle and not a red X in the upper right corner.
- Send a Command: select the ‘Commanding’ tab, search for led.BLINKING_ON_OFF and send it with the argument set to OFF.
- Verify Event: select the ‘Events’ tab and verify that the SetBlinkingState event reports the blinking state was set to OFF.
- Repeat steps 2 and 3 to turn the LED ON.
- Verify Telemetry: select the ‘Channels’ tab and verify that the LedBlinker telemetries appear.
Conclusion
You’ve successfully developed flight software capable of simulating communication and receiving data from a spacecraft. Congratulations on completing this lab!