Developer Tools: Starting a New Project

Overview

Starting a new firmware project entails a number of steps for it to integrate cleanly with the nuttx architecture and KConfig build system. This section covers in detail the steps needed to begin your project.

You should have already performed all of the steps under Setup Environment and be familiar with the Build from Source section. Build from Source also outlines the necessary changes to your Android application. The variable $BUILD_TOP refers to the path where you have synced your firmware repositories.

Firmware Configuration

There are four main stages to firmware development:

  1. Selecting your Protocols
  2. Creating your basic project files
  3. Customizing your nuttx and Greybus support
  4. Writing your source code

Selecting Your Protocols

The Moto Mod’s Manifest file informs the Moto Z which protocols should be enumerated. It does not contain details how they are connected, but simply whether they exist. The Manifest also contains other fields, such as the Moto Mod’s name and its associated APK. Manifest files are stored in the $BUILD_TOP/nuttx/apps/greybus-utils/manifests directory.

Below are the contents of the hdk_temperature.mnfs example:

[manifest-header]
version-major = 0
version-minor = 1
Version of the Manifest file
[interface-descriptor]
vendor-string-id = 1
product-string-id = 2
; Interface vendor string (id can't be 0)
[string-descriptor 1]
string = Motorola Mobility, LLC
; Interface product string (id can't be 0)
[string-descriptor 2]
string = MDK-TEMPERATURE
Vendor and Product String definitions
; Control protocol on CPort 0
[cport-descriptor 0]
bundle = 0
protocol = 0x00
; Control protocol Bundle 0
[bundle-descriptor 0]
class = 0
; Motorola specific on CPort 1
[cport-descriptor 1]
bundle = 1
protocol = 0xff
; Debug related Bundle 1
[bundle-descriptor 1]
class = 0xff
Greybus and Moto Mod interfaces for enumeration, communication, and control.

Protocol 0xff is for debug and logging support.
; Battery on CPort 2
[cport-descriptor 2]
bundle = 2
protocol = 0x08

; PTP on CPort 3
[cport-descriptor 3]
bundle = 2
protocol = 0xef

;Battery related Bundle 2
[bundle-descriptor 2]
class = 0x08
Battery and Power Transfer Protocols for metering and charging.

Note that these two protocols are grouped into a common bundle. This means both protocols must enumerate properly for either to be available.
; RAW interface on CPort 4
[cport-descriptor 4]
bundle = 3
protocol = 0xfe
Raw Protocol, used for custom control and data channel.

The descriptor for each protocol is defined by the greybus_protocol structure found in $BUILD_TOP/nuttx/apps/greybus-utils/greybus_manifest.h. When bundles are used, convention is to use the primary protocol descriptor as the bundle’s class.

Rather than starting from scratch, two templates have been created. Use hdk.mnfs for projects that do not utilize a battery or charging, or hdk-powered.mnfs if they do.

Creating the Basic Project Files

When working with the nuttx platform, the basic firmware definition begins with a Project. There are 3 files that are a part of every Project, which reside under $BUILD_TOP/nuttx/nuttx/configs/hdk/muc. You’ll note that all MDK projects exist under the single git branch. It is recommended that you create a new branch for each of your projects to help keep your src directory from becoming unwieldy.

Make.defs Makefile definitions
setenv.sh Script to configure build environment for this project
defconfig Top level Kconfig project definition Modified via “make menuconfig”

Like the Manifest, two base projects have been created for you to begin. base-powered should be used if your project contains a battery, or base-unpowered otherwise. Make a new directory for your project, then copy these three files into there.

Now you should make your project the active one:

$ cd $BUILD_TOP/nuttx/nuttx
$ make distclean
$ cd $BUILD_TOP/nuttx/nuttx/tools
$ ./configure.sh hdk/muc/project_name
$ cd $BUILD_TOP/nuttx/nuttx
$ make

Note, when switching projects it is important to run "make distclean" to ensure no build artifacts exist.

The configure script will copy the defconfig file to $BUILD_TOP/nuttx/nuttx/.config. When make menuconfig is used to modify the project definitions, the .config file is modified.

Tip: It is imperative that any changes made to the .config, through either make menuconfig or manual edits, be copied back over the project's defconfig so it is saved properly in the git repository. Otherwise, running a distclean or new configure to switch projects will lose all of your changes.

Customizing your nuttx and Greybus support

After creating your basic Project files, it’s time to begin customizing the firmware components. The first step is to actually create the project in the Kconfig build system. The top level Kconfig resides at $BUILD_TOP/nuttx/nuttx/configs/hdk/muc/Kconfig. Using the Temperature Sensor as an example:

config MODS_RAW_Temperature
    bool "Temperature Personality Mods Raw support"
    default n
    depends on GREYBUS_RAW
    select DEVICE_CORE
    select STM32_ADC
    select STM32_ADC_SWTRIG
    ---help---
        Enable Temperature Personality Raw support

This creates a #define CONFIG_MODS_RAW_TEMPERATURE=y which should wrap your source code changes. See here for further detail on Kconfig in nuttx. Once the project is completed, menuconfig can be used for further configuration.

$ cd $BUILD_TOP/nuttx/nuttx
$ make menuconfig

Set the VID/PID

One of the first things to do will be to update your firmware with your Project’s actual VID and PID. If a VID has not been assigned, the Developer VID (0x42) should be used for your development. You control how you choose to assign PIDs within your VID.

From the top level menu, select “System Type”:

Then scroll down to Board VID and Board PID and update the fields for your Project:

Once modified, select Save. Then Exit to the top level of menuconfig.

Select the Project

Next is to select and enable the Project you created earlier when editing Kconfig. From the top level menu, select “Board Selection”:

Then scroll down to your Project and press spacebar to select the Project you created:

Again, select Save and then Exit to the top level menu.

Assign your Manifest File

Now that the base project configuration is complete, the next step is to point that configuration at your custom Manifest.

From the top level, select “Application Configuration”:

Then select "Greybus Utility":

Finally, select “manifest name” and enter your manifest’s name. You do not need to include the .mnfs extension.

Again, select “Save” and then “Exit” to the top level menu.

Setup Raw Support

To enable the necessary protocols in the firmware, they must be set up. These all reside under the “Device Drivers” from the top level menu:

And then “Greybus Support” for the list of protocols:

Finally, the Temperature Personality Card uses the Raw protocol. Scroll down to “Vendor Raw Support” and press the spacebar to toggle it on:

Again, select “Save” and then “Exit” to the top level menu.

Enable Analog to Digital Conversion

The MuC has a subset of GPIO pins that can be used as an ADC (in the MDK, only pin PC3 can be used for ADC). Since the Temperature Personality uses an ADC to read the temperature from a thermistor.

First, select “System Type” from the top level menu:

Then scroll down to "STM32 Peripheral Support” and press Enter to select it:

Then scroll to "ADC1" and press the spacebar to toggle it on:

Again, select “Save” and then “Exit” to the top level menu.

Now that the ADC1 peripheral is configured, setup the ADC device itself. This resides under the “Device Drivers” from the top level menu:

Then scroll down to “Analog Device (ADC/DAC) Support” and press spacebar to toggle it on. Once enabled, press Enter to access the ADC sub-menu:

Scroll down to “Analog-to-Digital Conversion” and press the spacebar to toggle it on:

Again, select “Save” and then “Exit” to the top level menu.

Commit your changes

As mentioned earlier, when the configure script is run for your project, the defconfig file is copied into $BUILD_TOP/nuttx/nuttx/.config which is modified by menuconfig. To ensure these changes are maintained when switching projects, always copy the .config file back over your $BUILD_TOP/nuttx/nuttx/config/hdk/muc/{project_name}/defconfig. After doing so, before building, please perform another make distclean and rebuild.

Write your Source Code

Update the Makefile

Add the new source files (that you will create) to the Make system. Typically, there is a file that acts as the primary handler for each protocol. In the $BUILD_TOP/nuttx/nuttx/configs/hdk/muc/src/Makefile, add your source files wrapped in the CONFIG you created with your new project:

ifeq ($(CONFIG_MODS_RAW_TEMPERATURE),y)
    CSRCS += stm32_modsraw_temperature.c
endif

All source files for your project should be included in the CSRCS list to ensure they are compiled and linked.

Setup Your Device Driver

In your source file (for example, stm32_modsraw_temperature.c), various structures need to be created to integrate with nuttx. Three separate structures are required: device_driver, device_driver_ops, and device_XXX_type_ops where XXX is the protocol you are implementing. The protocol structures can be found in $BUILD_TOP/nuttx/nuttx/include/nuttx, as are the types used in the device_driver.

struct device_driver

The device_driver structure contains the high level configuration for your device, and has a pointer to the next level device_driver_ops structure. Using the Sensor Example:

struct device_driver mods_raw_temperature_driver = {
    .type = DEVICE_TYPE_RAW_HW,
    .name = "mods_raw_temperature",
    .desc = "Temperature Sensor Raw Interface",
    .ops = &temperature_driver_ops,
};
struct device_driver_ops

Now that the device_driver is setup, the next level device_driver_ops structure provides pointer functions to probe and remove the device. And, like the device_driver, the device_driver_ops structure points to the next level device_raw_type_ops structure. Using the Sensor Example:

static struct device_driver_ops temperature_driver_ops = {
    .probe = temp_raw_probe,
    .remove = temp_raw_remove,
    .type_ops = &temp_raw_type_ops,
};
struct device_XXX_type_ops

Finally, we get to the operations that actually handle the processing. These structures vary per protocol, but one common feature are callbacks. These deceptively named functions are used by greybus to provide your driver with functions to SEND data to the Moto Z. After probing your driver, nuttx calls register_callback and provides the function pointer for your driver to use. Using the Sensor Example:

static struct device_raw_type_ops temp_raw_type_ops = {
    .recv = temp_raw_recv,
    .register_callback = temp_raw_register_callback,
    .unregister_callback = temp_raw_unregister_callback,
};

Note that Raw also contains a recv operation. This is called when the Moto Mod receives data over Raw from the Moto Z. Your driver would then use the callback function to send its response.

For full source code and implementation, refer to the stm32_modsraw_temperature.c example.

As a final step, the new nuttx needs to be aware of the new drivers. This is done in $BUILD_TOP/nuttx/nuttx/configs/hdk/muc/src/stm32_boot.c.

In the devices[] array, add the following:

#ifdef CONFIG_MODS_RAW_TEMPERATURE
    {
        .type = DEVICE_TYPE_RAW_HW,
        .name = "mods_raw_temperature",
        .desc = "Temperature Sensor Raw Interface",
        .id   = 0,
    },
#endif

The type, name, and desc fields should match those in your device_driver structure.

And in the board_initialize() function, add:

#ifdef CONFIG_MODS_RAW_TEMPERATURE
  extern struct device_driver mods_raw_temperature_driver;
  device_register_driver(&mods_raw_temperature_driver);
#endif

The driver is now fully integrated into nuttx and will be enumerated at boot time. You’re ready to develop your product functionality!