Git Product home page Git Product logo

harp.core.rp2040's Introduction

Harp Core RP2040

An RP2040 Harp Core that implements the Harp Protocol to serve as the basis of a custom Harp device.

Features

  • Synchronization to an external Harp Clock Synchronizer signal.
  • Parsing incoming harp messages
  • Dispatching messages to the appropriate register
  • Sending harp-compliant timestamped replies

Examples

See the examples folder to get a feel for incorporating the harp core into your own project.

Additionally, here are a few examples that use the RP2040 Harp Core as a submodule in the wild:


Using this Library

The easiest way to use this library is to include it as submodule in your project. To see how to structure your project to incorporate the RP2040 Harp Core as a library, see the examples above--or read on.

Install the Pico SDK

Download (or clone) the Pico SDK to a known folder on your PC. (This folder does not need to be a sub-folder of your project.) From the Pico SDK project root folder, install the Pico SDK's dependencies with:

git submodule update --init --recursive

Install the Harp Core as a submodule

Next, in a sub-folder of your project, add harp.core.rp2040 as a submodule with:

git submodule add [email protected]:AllenNeuralDynamics/harp.core.rp2040.git

Setup your Project's CMakeLists.txt

At the top of your project's CMakeLists.txt, you will need to include and initialize the Pico SDK. You can do so with:

include(${PICO_SDK_PATH}/pico_sdk_init.cmake)
pico_sdk_init()

Your must also point to the folder of the Harp.Core.RP2040's CMakeLists.txt with

add_subdirectory(/path/to/cmakelist_dir build) # Path to harp.core.rp2040's CMakeLists.txt

(Note that you must change path/to/cmakelist_dir above to the actual path of this project's CMakeLists.txt.)

At the linking step, you can link against the Harp core libraries with:

target_link_libraries(${PROJECT_NAME} harp_core harp_sync)

Point to the Pico SDK

Recommended, but optional: define the PICO_SDK_PATH environment variable to point to the location where the pico-sdk was downloaded. i.e:

PICO_SDK_PATH=/home/username/projects/pico-sdk

On Linux, it may be preferrable to put this in your .bashrc file.

Compiling the Firmware

Without an IDE

From this directory, create a directory called build, enter it, and invoke cmake with:

mkdir build
cd build
cmake ..

If you did not define the PICO_SDK_PATH as an environment variable, you must pass it in here like so:

mkdir build
cd build
cmake -DPICO_SDK_PATH=/path/to/pico-sdk ..

After this point, you can invoke the auto-generated Makefile with make

Flashing the Firmware

Press-and-hold the Pico's BOOTSEL button and power it up (i.e: plug it into usb). At this point you do one of the following:

  • drag-and-drop the created *.uf2 file into the mass storage device that appears on your pc.
  • flash with picotool

Using Bonsai

Native packages exist in Bonsai for communicating with devices that speak Harp protocol. For more information on reading data or writing commands to your custom new harp device, see the Harp Tech Bonsai notes.


Developer Notes

Debugging with printf

The Harp Core consumes the USB serial port, so printf messages must be rerouted to an available UART port. The Pico SDK makes this step fairly straightforward. Before calling printf you must first setup a UART port with:

#define UART_TX_PIN (0)
#define BAUD_RATE (921600)
stdio_uart_init_full(uart0, BAUD_RATE, UART_TX_PIN, -1) // or uart1, depending on pin

To read these messages, you must connect a 3.3V FTDI Cable to the corresponding pin and connect to it with the matching baud rate.

Additionally, in your CMakeLists.txt, you must add

pico_enable_stdio_uart(${PROJECT_NAME} 1) # UART stdio for printf.

for each library and executable using printf and you must link it with pico_stdlib.

Debugging the Core

printf messages are sprinkled throughout the Harp Core code, and they can be conditionally compiled by adding flags to your CMakeLists.txt.

To print out details of every outgoing (Device to PC) messages, add:

add_definitions(-DDEBUG_HARP_MSG_OUT)

To print out details of every received (PC to Device) messages, add:

add_definitions(-DDEBUG_HARP_MSG_IN)

References

harp.core.rp2040's People

Contributors

poofjunior avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

harp.core.rp2040's Issues

Sync Plug is not hot-swappable

As-is, the cable cannot be removed from jack without causing the device to reset.

This is probably not a feature we care about, but it's worth noting here.

Speed improvements to sending Harp replies.

There are a few avenues to updating the Harp reply routine, all of which could offer significant performance benefits.

First, Tinyusb supports a multi-byte write tud_cdc_write that's likely to be faster than just calling tud_cdc_write_char. (We currently write chars one-at-a-time such that we can also calculate the checksum.)

However, if we know where the underlying data is being transferred, we may be able to do a DMA transfer to its final location while also computing the checksum automatically. (See RP2040 datasheet 2.5.5.2.)

It's worth investigating if we can speed up this function at all.

Explore generating RegSpecs list automatically

The list of RegSpecs is per-register supplementary information (example) needed to dispatch a Harp reply. This information could almost be generated with a compile-time LUT (example) if only we could iterate through the fields of a struct.

Enter Boost PFR (also magic_get), a library that lets you iterate over struct fields at compile time (example).

Reasons to implement this:

  • It simplifies user-facing interface for creating a harp device

Reasons not to do this:

  • It makes compilation across different setups more difficult
  • It increases code size significantly
  • it produces confusing errors for new users.
  • the implementation becomes more complicated for inferring some sort of "composite" payload type where the payload is some sort of struct of mixed fields.

Details

We would need a helper function to infer the register type from the register at compile time. This can be done with function overloading and constexpr.

constexpr reg_type_t to_reg_type(uint8_t value) {return U8};
constexpr reg_type_t to_reg_type(int8_t value) {return S8};
constexpr reg_type_t to_reg_type(uint16_t value) {return U16};
constexpr reg_type_t to_reg_type(int16_t value) {return S16};
constexpr reg_type_t to_reg_type(uint32_t value) {return U32};
constexpr reg_type_t to_reg_type(int32_t value) {return S32};
constexpr reg_type_t to_reg_type(uint64_t value) {return U64};
constexpr reg_type_t to_reg_type(int64_t value) {return S64};
constexpr reg_type_t to_reg_type(float value) {return Float};

Harp Core DUMP must dump app registers also

The implementation of the DUMP bit in R_OPERATION_CTRL is to serialize and dispatch all registers (app and core). The current implementation has no knowledge of any "App" registers and only sends the harp core registers.

Make all public member functions static

Since there is only one harp instance, it would useful to make the public-facing API consistent. (This also makes it easy to write functions that attach callbacks to HarpCore static member functions before the HarpCore has been instantiated.)

Enable picotool reset_interface functionality

In our local copy of usb_descriptors.c, it is not sufficient to set
#define PICO_STDIO_USB_ENABLE_RESET_VIA_VENDOR_INTERFACE (1).

What I'm seeing by setting the #define statement is that lsusb will show that a Reset interface descriptor does indeed exist, but the device hangs when we try to do anything with it.

That's most likely(?) because the pico-sdk's reset_interface.c is not being populated if we link to tinyusb_device in our CMakeLists.txt. We may need to make a local copy of the callback functions if we want to be able to use picotool (i.e: to reset the device from the PC without physically touching the BOOTSEL button).

Paste of lsusb output with Reset interface descriptor info shown below:

    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        2
      bAlternateSetting       0
      bNumEndpoints           0
      bInterfaceClass       255 Vendor Specific Class
      bInterfaceSubClass      0 
      bInterfaceProtocol      1 
      iInterface              5 Reset

References

Harp Core "DUMP" implementation is incorrect

Writing a 1 to the DUMP bit field in the R_OPERATION_CTRL register is supposed to send out one harp reply message per register, not one harp reply with a payload containing all register contents. This nuance isn't really captured in the current docs, so it's worth clearing that up too.

Implement (to-be-defined) spec for bootloader mode

As is, there is no clearly-defined protocol transaction to put the device into bootloader mode to accept new firmware.

For production purposes, it would be extremely valuable to be able to configure the device to accept a firmware device such that we don't need to physically dig every device instance out of every setup to update firmware.

First Draft TODOs

  • parse messages
  • dispatch messages to the appropriate callback function.
  • Handle replying to read requests.
  • time keeping/updating from Harp Message SECONDS and MICROSECONDS register writes.
  • populating reg name field on initialization.
  • parse harp messages
  • send harp replies (read reply, write reply, event reply)
  • handle time keeping/updating from clock synchronizer.
  • Implement key register flag settings.
  • enable inheritance in a way that extends original register set.
  • save non-volatile register data to eeprom
  • check checksum

Register flag settings:

  • R_OPERATION_CTRL: DUMP
  • R_OPERATION_CTRL: MUTE_RPL
  • R_OPERATION_CTRL: VISUALEN

Other:

  • Easier way to debug. printf should be init on uart.
  • queue outgoing harp messages.
  • queue incoming harp messages.

Remove pico sdk submodule

It's likely that this repo will be submoduled, so it's preferrable that we remove the pico-sdk as a submodule and instead have people define PICO_SDK_PATH.

Firmware README instructions will need to be updated as well.

Strictly use TinyUSB for better device agnosticism

All of our USB interactions should be handled via TinyUSB as a CDC device.

Removing pico-sdk-specific calls in favor of TinyUSB is a key step in making this harp core useable on other platforms. In this case, the range would broaden to include any microcontroller that supports TinyUSB.

We will need a TinyUSB CDC config, but we can reuse the existing ones for stdio.

Reading Timestamp should reference a cached offset

There is a race condition where it is possible for the reported Timestamp seconds and microseconds to reference a different offset and therefore report an erroneous value. This happens if the timestamp offset is updated via interrupt while the timestamp is being added into the reply message.

The fix would be to make sure we reference the same offset when we retrieve the time and compute it to update the timestamp registers and then read them. It really just boils down to making sure that the local offset value (offset_us_64_) is either a monotonic read or read from a double-buffer.
https://github.com/AllenNeuralDynamics/harp.core.rp2040/blob/main/firmware/inc/harp_core.h#L351

Expose Configurable USB fields from a config file.

We should expose some override-able fields that will give us the ability to define USB-related device descriptor data.
This info is especially useful for figuring out which device to connect to among a sea of devices. This info would also be super useful to create a tool to update the firmware of a particular device.

These should include:

  • vendor id
  • product id
  • device description

To actually use valid Vendor/Product IDs, it's worth requesting a PID from https://pid.codes/

That way, our setups can fit in an ecosystem of everyday products.

Scheduled Events

According to the protocol, controller-to-device messages do not include timestamps. If the protocol were to add timestamps, the device could interpret these messages as "do-this-read/write@timestamp," i.e: a scheduled register read or write at a future time.

To handle scheduled/read events, we would need to incorporate a way for handling a schedule of messages. A priority queue would be a great choice here, the ETL has an existing data structure for this, and I've written a snippet of source code in the past to demonstrate how this could work without dynamic memory allocation and a low memory footprint.

It's worth mentioning that messages handled at a later time need to be copied out of the receive buffer. Memcpy would probably be fine here, but we could even borrow a DMA channel to move the data without blocking.

Add a "fast time_us_64"

The Pico-SDK has a slightly slower implementation of reading the 64-bit timestamp registers, whereas reading the latching register directly (with interrupts disabled) is twice as fast.

inline uint64_t time_us_64_fast()
{
    uint32_t status = save_and_disable_interrupts();
    uint64_t time_us = timer_hw->timelr; // Locks time until we read TIMEHR.
    time_us |= (uint64_t(timer_hw->timerhr) << 32);
    restore_interrupts(status);
    return time_us;
}

And if you can guarantee that you don't call this from interrupts, you could omit the calls to save-and-restore interrupts too.

inline uint64_t time_us_64_fast_unsafe()
{
    uint64_t time_us = timer_hw->timelr; // Locks time until we read TIMEHR.
    time_us |= (uint64_t(timer_hw->timerhr) << 32);
    return time_us;
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.